diff --git a/pyproject.toml b/pyproject.toml index 0f972fa5..f6390d1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "stevedore>1.20.0", "setuptools", "packaging", + "resolvelib", "psutil; sys_platform=='win32'", "psutil; sys_platform=='linux'", "psutil; sys_platform=='linux2'", diff --git a/src/e3/python/pypi.py b/src/e3/python/pypi.py index bdcaba61..ea42dc9f 100644 --- a/src/e3/python/pypi.py +++ b/src/e3/python/pypi.py @@ -1,81 +1,405 @@ from __future__ import annotations +from typing import TYPE_CHECKING +import tarfile import json -import logging -import requests +import e3.log import re +from e3.python.wheel import Wheel +from e3.fs import cp, mkdir +from operator import attrgetter import os -import time -import packaging.version -import packaging.tags -from packaging.specifiers import Specifier +import requests +from urllib.parse import urlparse +from resolvelib import BaseReporter, Resolver +from resolvelib.providers import AbstractProvider +from resolvelib.resolvers import ResolutionImpossible +from packaging.requirements import Requirement from packaging.utils import canonicalize_name -from typing import TYPE_CHECKING +from html.parser import HTMLParser +from packaging.version import Version, InvalidVersion from e3.error import E3Error -from e3.python.wheel import Wheel -from e3.fs import cp -from pkg_resources import Requirement if TYPE_CHECKING: - from typing import Any from types import TracebackType + from typing import Mapping, Iterator, Sequence, Iterable, Any + from resolvelib.structs import Matches + from resolvelib.providers import Preference + from resolvelib.resolvers import RequirementInformation -logger = logging.getLogger("e3.python.pypi") -PLATFORM_SYSTEMS = {"darwin": "Darwin", "win32": "Windows", "linux": "Linux"} +logger = e3.log.getLogger("e3.python.pypi") class PyPIError(E3Error): pass -class PackageFile: - """A release package file as represented in PyPI.""" +def get_pip_env(platform: str, python_version: Version) -> dict[str, str]: + """Return an environment used by pip to match requirements markers. + + :param platform: a platform (e3 format) + :param python_version: the Python version to consider + """ + # We always consider Cpython + pv = python_version + result = { + "implementation_name": "cpython", + "platform_python_implementation": "CPython", + "implementation_version": f"{pv.major}.{pv.minor}.{pv.micro}", + "python_full_version": f"{pv.major}.{pv.minor}.{pv.micro}", + "python_version": f"{pv.major}.{pv.minor}", + } + + # Fill platform informations + if platform.endswith("-darwin"): + result["sys_platform"] = "darwin" + elif platform.endswith("-linux"): + result["sys_platform"] = "linux" + elif platform.endswith("-windows") or platform.endswith("-windows64"): + result["sys_platform"] = "win32" + else: + raise PyPIError(f"Non supported platform {platform}") + + result["platform_system"] = { + "darwin": "Darwin", + "win32": "Windows", + "linux": "Linux", + }[result["sys_platform"]] + + result["os_name"] = { + "darwin": "posix", + "win32": "nt", + "linux": "posix", + }[result["sys_platform"]] + + # ??? add missing platform_machine + return result + + +class PyPILink: + """Link returned by PyPI simple API.""" + + def __init__(self, url: str, yanked: str | None, has_metadata: bool) -> None: + """Initialize a PyPI link. + + :param url: url of the resource + :param yanked: yanker data + :param has_metadata: True if metadata is directly available from PyPI + """ + self.url = url + self.yanked = yanked + self.has_metadata = has_metadata + + @property + def is_yanked(self) -> bool: + """Return True if the package is yanked.""" + return self.yanked is not None - def __init__(self, pypi: PyPIClosure, *, data: dict["str", Any]) -> None: - """Initialize a package file. + @property + def metadata_url(self) -> str: + """Return the metadata url.""" + return self.url + ".metadata" + + def as_dict(self) -> dict[str, None | bool | str]: + """Serialize the a PyPILink into a Python dict that can be dump as json.""" + return { + "url": self.url, + "yanked": self.yanked, + "has_metadata": self.has_metadata, + } + + @classmethod + def from_dict(cls, data: dict) -> PyPILink: + """Transform a generic dict into a PyPILink. - :param pypi: the pypi session - :param data: json data for the package file + :param data: the dict to read """ - self.data = data + return PyPILink( + url=data["url"], yanked=data["yanked"], has_metadata=data["has_metadata"] + ) - # Get metadata from filename if possible - if self.is_wheel: - _, _, py_tags, abi_tags, platform_tags = self.filename[:-4].split("-") + +class PyPILinksParser(HTMLParser): + """HTML parser to parse links from the PyPI simple API.""" + + def __init__(self) -> None: + """Initialize the parser.""" + super().__init__() + self.links: list[PyPILink] = [] + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + """See HTMLParser doc.""" + if tag == "a": + attr_dict = dict(attrs) + assert attr_dict["href"] is not None + self.links.append( + PyPILink( + url=attr_dict["href"], + yanked=attr_dict.get("data-yanked"), + has_metadata="data-dist-info-metadata" in attr_dict, + ) + ) + + +class PyPI: + def __init__( + self, + pypi_url: str = "https://pypi.org/", + allowed_yanked: list[str] | None = None, + allowed_prerelease: list[str] | None = None, + cache_dir: str = "pypi.cache", + ) -> None: + """Interface to a PYPI simple API. + + :param pypi_url: URL to pypi + :param allowed_yanked: list of projects for which yanked releases are accepted + """ + self.pypi_url = pypi_url + + # Normalize the URL + if not self.pypi_url.endswith("/"): + self.pypi_url += "/" + + # Normalize list of package for which yanked releases are acceptable + if allowed_yanked is not None: + self.allowed_yanked = {canonicalize_name(el) for el in allowed_yanked} + else: + self.allowed_yanked = set() + + if allowed_prerelease is None: + self.allowed_prerelease = set() else: - py_tags = "" - abi_tags = "" - platform_tags = "" + self.allowed_prerelease = { + canonicalize_name(el) for el in allowed_prerelease + } + + self.cache_dir = os.path.abspath(cache_dir) + self.cache: dict[str, list[PyPILink]] = {} + self.candidate_cache: dict[str, list[PyPICandidate]] = {} + + @property + def pypi_cache_file(self) -> str: + """Get location of file containing result of pypi requests.""" + return os.path.join(self.cache_dir, "pypi-cache.json") + + def fetch_project_links(self, name: str) -> list[PyPILink]: + """Fetch list of resource for a given Python package. + + :param name: Python package name + :return: a list of dict containing the link to each resource along with + some metadata + """ + identifier = canonicalize_name(name) + if identifier not in self.cache: + logger.debug(f"fetch {identifier} links from {self.pypi_url}") + pypi_request = requests.get(self.pypi_url + "simple/" + identifier + "/") + pypi_request.raise_for_status() + pypi_links_parser = PyPILinksParser() + pypi_links_parser.feed(pypi_request.text) + + # Update cache + self.cache[identifier] = pypi_links_parser.links + return self.cache[identifier] + + def fetch_candidates( + self, identifier: str, env: dict[str, str], extras: set[str] + ) -> list[PyPICandidate]: + """Return a list of candidates for a given package, env and list of extras. + + :param identifier: a normalized python package name or internal identifier + computed by the provider + :param env: the pip environment required + :param extras: set of needed extras + """ + if identifier not in self.candidate_cache: + self.candidate_cache[identifier] = [] + project_links = self.fetch_project_links(identifier.split("@", 1)[0]) + for link in project_links: + try: + c = PyPICandidate( + link=link, + env=env, + extras=extras, + cache_dir=os.path.join(self.cache_dir, "resources"), + ) + # Discard prerelease unless explicitely allowed + if ( + c.version.is_prerelease + and c.name not in self.allowed_prerelease + ): + continue + + # Discard yanked releases unless explicitely allowed + if c.is_yanked and c.name not in self.allowed_yanked: + continue + + self.candidate_cache[identifier].append(c) + except InvalidVersion: + continue + return self.candidate_cache[identifier] + + def save_cache(self) -> None: + """Dump cache to disk.""" + mkdir(self.cache_dir) + with open(self.pypi_cache_file, "w") as fd: + fd.write( + json.dumps( + {k: [el.as_dict() for el in v] for k, v in self.cache.items()}, + indent=2, + ) + ) + def load_cache(self) -> None: + """Load cache from disk.""" + if os.path.isfile(self.pypi_cache_file): + with open(self.pypi_cache_file) as fd: + self.cache = { + k: [PyPILink.from_dict(el) for el in v] + for k, v in json.load(fd).items() + } + + +class PyPICandidate: + def __init__( + self, + link: PyPILink, + env: dict[str, str], + extras: set[str], + cache_dir: str, + ) -> None: + """Initialize a Candidate. + + :param link: data return by PyPI simple API + :param env: the environment used to evaluate requirements markers + :param extras: list of extras that should be included + :param cache_dir: cache location in which resources are downloaded + """ + self.url = link.url + self.is_yanked = link.is_yanked + self.has_direct_metadata = link.has_metadata + self.extras = set(extras) + self.cache_dir = os.path.abspath(cache_dir) + + # Compute filename and extract compatibility information + path = urlparse(self.url).path + self.filename = path.rpartition("/")[-1] + + py_tags = "" + abi_tags = "" + platform_tags = "" + + if self.filename.endswith(".whl"): + # Wheel filenames contain compatibility information + tmp, py_tags, abi_tags, platform_tags = self.filename[:-4].rsplit("-", 3) + name, version = tmp.split("-", 1) + + elif self.filename.endswith(".tar.gz"): + name, version = self.filename[:-7].split("-", 1) + else: + basename, ext = os.path.splitext(self.filename) + name, version = basename.split("-")[:2] + + self.name = canonicalize_name(name) + self.version = Version(version) self.py_tags = py_tags.split(".") self.abi_tags = abi_tags.split(".") self.platform_tags = platform_tags.split(".") - self.pypi = pypi - @property - def kind(self) -> str: - """Return the package kind.""" - return self.data["packagetype"] + # Requirements cache + self._reqs: None | set[Requirement] = None - @property - def filename(self) -> str: - """Return the package filename.""" - return self.data["filename"] + # Copy of the environment used to evaluate markers + self.env = dict(env) @property - def url(self) -> str: - """Return the download url.""" - return self.data["url"] + def is_wheel(self) -> bool: + """Check if resource is a wheel.""" + return self.filename.endswith(".whl") - @property - def is_yanked(self) -> bool: - """Return whether the package is yanked.""" - return self.data.get("yanked", False) + def download(self) -> str: + """Download the file in the PyPI cache. + + :return: the location of the file + """ + download_path = os.path.join(self.cache_dir, self.filename) + if not os.path.isfile(download_path): + mkdir(self.cache_dir) + if self.url.startswith("file://"): + cp(self.url.replace("file://", "", 1), download_path) + else: + answer = requests.get(self.url, stream=True) + with open(download_path, "wb") as fd: + fd.write(answer.content) + return download_path @property - def is_wheel(self) -> bool: - """Return whether the package is a wheel.""" - return self.kind == "bdist_wheel" + def requirements(self) -> set[Requirement]: + """Return the list of requirements for the package. + + :return: a set of Requirement + """ + # Check if the requirements have already been computed + if self._reqs is not None: + return self._reqs + + reqs: set[Requirement] = set() + + if self.is_wheel: + # This is a wheel so there is a formal way to get the metadata. + wheel_path = self.download() + reqs |= Wheel(path=wheel_path).requirements + + elif self.filename.endswith(".tar.gz"): + # This is a .tar.gz archive so we might find some info about the + # requirements either in the egg-info data for older packages or + # as fallback in a requirements.txt file. + path = self.download() + with tarfile.open(name=path, mode="r:gz") as fd: + egg_info = f"{self.filename[:-7]}/{self.name}.egg-info" + egg_info_requires = f"{egg_info}/requires.txt" + requirements_txt = f"{self.filename[:-7]}/requirements.txt" + archive_members = fd.getnames() + + if egg_info in archive_members: + # If we egg-info data without requires.txt it means the package + # has no dependencies. + if egg_info_requires in archive_members: + file_fd = fd.extractfile(egg_info_requires) + assert file_fd is not None + requires = file_fd.read().decode("utf-8") + reqs |= { + Requirement(line.strip()) for line in requires.splitlines() + } + + elif requirements_txt in archive_members: + # Check if there is a requirements.txt (this is a fallback) + file_fd = fd.extractfile(requirements_txt) + assert file_fd is not None + requires = file_fd.read().decode("utf-8") + reqs |= { + Requirement(line.strip()) for line in requires.splitlines() + } + + else: + logger.warning(f"Cannot follow dependencies of package {self.name}") + + # Once we have the complete list of requirements, use the env to filter + # out requirements not needed for the current configuration. + self._reqs = set() + if self.extras: + # Special handling for extras. An additional dependencies is added + # to the package itself without extras + for extra in self.extras: + self.env["extra"] = extra + for r in reqs: + if r.marker is not None and r.marker.evaluate(self.env): + self._reqs.add(r) + self._reqs.add(Requirement(f"{self.name} == {self.version}")) + else: + for r in reqs: + if r.marker is None or r.marker.evaluate(self.env): + self._reqs.add(r) + return self._reqs def is_compatible_with_platforms(self, platform_list: list[str]) -> bool: """Check if the package is compatible with a list of platform. @@ -107,22 +431,8 @@ def is_compatible_with_platforms(self, platform_list: list[str]) -> bool: if re.match("|".join(platforms_regex), platform) ) ) - logger.debug(f"{self.filename} compatible with {platform_list}: {result}") return result - @property - def is_generic_wheel(self) -> bool: - """Return whether the package is a generic package. - - If True then the wheel can be used on any Python 3.x version and on any - platform. - """ - return ( - "py3" in self.py_tags - and "none" in self.abi_tags - and "any" in self.platform_tags - ) - def is_compatible_with_cpython3(self, minor_version: int) -> bool: """Check whether the package is compatible with a given python 3 version. @@ -142,241 +452,120 @@ def is_compatible_with_cpython3(self, minor_version: int) -> bool: ) or "abi3" in self.abi_tags ) - logger.debug( - f"{self.filename} compatible with Python 3.{minor_version}: {result}" - ) return result - def download(self) -> str: - """Download the file in the PyPI cache. + @property + def is_generic_wheel(self) -> bool: + """Return whether the package is a generic package. - :return: the location of the file + If True then the wheel can be used on any Python 3.x version and on any + platform. """ - download_path = os.path.join(self.pypi.cache_dir, self.filename) - if not os.path.isfile(download_path): - if self.url.startswith("file://"): - cp(self.url.replace("file://", "", 1), download_path) - else: - answer = requests.get(self.url, stream=True) - with open(download_path, "wb") as fd: - fd.write(answer.content) - return download_path + return ( + "py3" in self.py_tags + and "none" in self.abi_tags + and "any" in self.platform_tags + ) - @property - def requirements(self) -> set[Requirement]: - """Return the list of requirements for the package. + def __repr__(self) -> str: + return f"{self.name}@{self.version} ({self.filename})" - Only dependencies for wheels can be tracked. - """ - if not self.is_wheel: - return set() + def __str__(self) -> str: + return f"{self.name}@{self.version} ({self.filename})" - wheel_path = self.download() - return Wheel(path=wheel_path).requirements # type: ignore +class PyPIProvider(AbstractProvider): + """Class that should declared to instanciate a resolver (see resolvelib doc).""" -class Package: - def __init__( - self, - pypi: PyPIClosure, - *, - data: dict["str", Any], - ) -> None: - """Initialize a package metadata object. + def __init__(self, env: dict[str, str], pypi: PyPI): + """Initialize the provider. - :param pypi: a PyPIClosure session - :param data: the data as fetched on pypi + :param env: a pip environment selected packages should match + :param pypi: an interface object to PyPI """ + super().__init__() self.pypi = pypi - self.data = data - self.extras = {""} - self.versions = [] - self.releases: dict[str, list[PackageFile]] = {} - self.sys_platforms: set[str] = set() - - for version in self.data.get("releases", {}): - try: - v = packaging.version.parse(version) - - if (not v.is_prerelease and not v.is_devrelease) or ( - v.is_prerelease and self.name in self.pypi.allowed_prerelease - ): - self.versions.append(v) - except Exception: - # Many packages uploaded before pip 1.4 had versions that are - # rejected by packaging.version. Do not warn but just log - # the error. - logger.debug(f"Cannot parse version {version} of {self.name}") - logger.debug(f"Load package {self.name}") - - def __eq__(self, other: Any) -> bool: - return isinstance(other, Package) and other.name == self.name - - def __hash__(self) -> int: - return hash(self.name) - - @property - def as_requirement(self) -> list[Requirement]: - """Return a list of requirement string for the package.""" - if len(self.sys_platforms) == len(PLATFORM_SYSTEMS): - return [Requirement.parse(f"{self.name}=={self.latest_version}")] - else: - return [ - Requirement.parse( - f"{self.name}=={self.latest_version}; sys_platform == '{p}'" - ) - for p in self.sys_platforms - ] - - @property - def name(self) -> str: - """Name of the package.""" - return self.data["info"]["name"].replace(".", "-") - - @property - def latest_release(self) -> list[PackageFile]: - """Return the latest release files.""" - if self.latest_version not in self.releases: - all_files = [ - PackageFile(pypi=self.pypi, data=el) - for el in self.data["releases"][self.latest_version] - ] - all_files = [ - f - for f in all_files - if f.is_compatible_with_cpython3(self.pypi.python3_version) - and f.is_compatible_with_platforms(self.pypi.platforms) - and ( - not f.is_yanked - or self.name in self.pypi.allowed_yanked - or self.name.replace("-", "_") in self.pypi.allowed_yanked - ) - ] - if any((f.is_generic_wheel for f in all_files)): - all_files = [f for f in all_files if f.is_wheel] - - if not all_files: - self.versions.remove(packaging.version.parse(self.latest_version)) - return self.latest_release - - self.releases[self.latest_version] = all_files - - return self.releases[self.latest_version] - - @property - def has_generic_wheel(self) -> bool: - """Return whether a generic wheel exists.""" - return any((f for f in self.latest_release if f.is_generic_wheel)) - - @property - def latest_release_requirements(self) -> set[Requirement]: - """Return the requirements for the latest release.""" - result = set() - for f in self.latest_release: - for r in f.requirements: - for extra in self.extras: - for sys_platform in self.pypi.sys_platforms: - if r.marker is None or r.marker.evaluate( - { - "python_version": f"3.{self.pypi.python3_version}", - "sys_platform": sys_platform, - "platform_system": PLATFORM_SYSTEMS[sys_platform], - "extra": extra, - } - ): - self.pypi.pkg(canonicalize_name(r.name)).sys_platforms.add( - sys_platform - ) - result.add(r) + self.env = env + self.candidate_cache: dict[str, PyPICandidate] = {} + + def identify(self, requirement_or_candidate: Requirement | PyPICandidate) -> str: + """See resolvelib documentation.""" + result: str = canonicalize_name(requirement_or_candidate.name) + if requirement_or_candidate.extras: + result += "@" + ",".join(sorted(requirement_or_candidate.extras)) return result - def closure(self, state: set[Package] | None = None) -> set[Package]: - """Return package closure. - - :param state: an initial set of packages (internal parameter) - :return: the closure of Packages associated with the most recent - suitable release. - """ - if state is None: - state = set() - - for r in self.latest_release_requirements: - p = self.pypi.add_requirement(r, explicit=False) - - if p not in state: - state.add(p) - state |= p.closure(state=state) - - return state - - def file_closure(self) -> set[str]: - """Return file closure for the package.""" - all_pkgs = {self} | self.closure() - all_files = set() - for pkg in all_pkgs: - logging.debug( - f"Add files from {pkg.name} {len(self.latest_release)} " - f"(from {self.name})" - ) - all_files |= {f.download() for f in pkg.latest_release} - return all_files - - @property - def latest_version(self) -> str: - """Return the latest version as str.""" - if not self.versions: - raise PyPIError( - f"Cannot find latest version for {self.name!r}: " - "No more suitable version" - ) - return str(max(self.versions)) - - def add_constraint(self, requirement: Requirement) -> None: - """Apply a new constraint to this package. - - The effect is mainly to remove versions of the current package that do not - match the constraint. PyPIError is raised in case no version is available - - :param requirement: a requirement to apply - """ - logging.debug(f"Apply constraint: {str(requirement)}") - # Check if requirement applies to that package - if canonicalize_name(requirement.name) != canonicalize_name(self.name): - return - - # Check platforms - for sys_platform in self.pypi.sys_platforms: - if requirement.marker is None or requirement.marker.evaluate( - { - "python_version": f"3.{self.pypi.python3_version}", - "sys_platform": sys_platform, - "platform_system": PLATFORM_SYSTEMS[sys_platform], - } - ): - self.sys_platforms.add(sys_platform) - current_length = len(self.versions) - - # Apply version constraints - for spec in requirement.specifier: - self.versions = [ - v - for v in self.versions - if Specifier( - f"{spec.operator}{spec.version}", - prereleases=self.name in self.pypi.allowed_prerelease, - ).contains(str(v)) - ] - - if len(self.versions) != current_length: - logging.debug( - f"Found {len(self.versions)} versions after applying constraints" - ) - if len(self.versions) == 0: - logger.critical(f"Cannot satisfy constraint {requirement}") - raise PyPIError(f"Cannot satisfy constraint {str(requirement)}") - - def __str__(self) -> str: - return f"{self.name}=={self.latest_version}" + def get_preference( + self, + identifier: str, + resolutions: Mapping[str, PyPICandidate], + candidates: Mapping[str, Iterator[PyPICandidate]], + information: Mapping[Any, Iterator[RequirementInformation[Any, Any]]], + backtrack_causes: Sequence[RequirementInformation], + ) -> Preference: + """See resolvelib documentation.""" + return sum(1 for _ in candidates[identifier]) + + def find_matches( + self, + identifier: str, + requirements: Mapping[str, Iterator[Requirement]], + incompatibilities: Mapping[str, Iterator[PyPICandidate]], + ) -> Matches: + """Return the list of candidates that match a given list of requirements.""" + # Get requirements that must be satisfied + reqs = list(requirements[identifier]) + + extras = set() + for r in reqs: + extras |= r.extras + + # And discarded versions + incomps = {c.version for c in incompatibilities[identifier]} + + # Fetch the list of releases for the given package + candidates = self.pypi.fetch_candidates(identifier, env=self.env, extras=extras) + result = [] + + version_has_wheel = {} + for c in candidates: + if c.filename.endswith(".whl"): + version_has_wheel[c.version] = True + + for candidate in candidates: + # We only handle wheels + + if not candidate.filename.endswith(".whl"): + if version_has_wheel.get(candidate.version): + continue + elif not candidate.filename.endswith(".tar.gz"): + continue + + if candidate.version in incomps: + continue + + match_all = True + for r in reqs: + if candidate.version not in r.specifier: + match_all = False + continue + + if match_all: + result.append(candidate) + + return sorted(result, key=attrgetter("version"), reverse=True) + + def is_satisfied_by( + self, requirement: Requirement, candidate: PyPICandidate + ) -> bool: + """See resolvelib documentation.""" + if canonicalize_name(requirement.name) != candidate.name: + return False + return candidate.version in requirement.specifier + + def get_dependencies(self, candidate: PyPICandidate) -> Iterable[Requirement]: + """See resolvelib documentation.""" + return candidate.requirements class PyPIClosure: @@ -385,11 +574,11 @@ class PyPIClosure: def __init__( self, *, - python3_version: int, + python3_version: str, platforms: list[str], cache_dir: str, cache_file: str | None = None, - pypi_url: str = "https://pypi.org/pypi", + pypi_url: str = "https://pypi.org/", allowed_prerelease: list[str] | None = None, allowed_yanked: list[str] | None = None, ) -> None: @@ -407,179 +596,104 @@ def __init__( :param allowed_yanked: list of package names authorized to have yanked flags set to true (see: PEP_592). """ - self.cache_file = cache_file - self.cache_dir = cache_dir - self.pypi_url = pypi_url - self.db: dict[str, Any] = {} - self.packages: dict[str, Package] = {} - self.load_cache_file() + # Pypi database + self.pypi = PyPI(pypi_url, allowed_yanked=allowed_yanked, cache_dir=cache_dir) self.requirements: set[Requirement] = set() - self.explicit_requirements: set[Requirement] = set() self.allowed_prerelease = allowed_prerelease or [] self.allowed_yanked = allowed_yanked or [] - self.platforms = platforms - self.sys_platforms = set() - for p in self.platforms: - if "darwin" in p: - self.sys_platforms.add("darwin") - if "linux" in p: - self.sys_platforms.add("linux") - if "windows" in p: - self.sys_platforms.add("win32") - - self.python3_version = python3_version - - def __enter__(self) -> PyPIClosure: - return self - - def __exit__( - self, - _type: type[BaseException] | None, - _val: BaseException | None, - _tb: TracebackType | None, - ) -> None: - self.save_cache_file() - - def pkg(self, pkg: str, extras: list[str] | None = None) -> Package: - """Fetch all metadata from PyPI for a given package. - - :param pkg: the package name - :param extras: list of extras to consider for the package. For example - if you need e3-core[test] extras should be set to ["test"]. None - all extras for a given package are ignored. - :return: a Package object - """ - # Normalize the package name - pkg = pkg.lower().replace("_", "-").replace(".", "-") - - pkg_extras = [""] - if extras is not None: - pkg_extras += extras - - if pkg not in self.packages: - # Fetch the raw data - if ( - pkg in self.db - and (time.time() - self.db[pkg].get("timestamp", 0.0)) < 24 * 3600.0 - ): - data_dict = self.db[pkg]["metadata"] - else: - answer = requests.get(f"{self.pypi_url}/{pkg}/json") - if answer.status_code != requests.codes.ok: - raise PyPIError( - f"Cannot fetch {pkg} metadata from {self.pypi_url} " - f"(return: {answer.status_code})" - ) - data_dict = answer.json() - self.db[pkg] = {"timestamp": time.time(), "metadata": data_dict} - self.packages[pkg] = Package(pypi=self, data=data_dict) - - self.packages[pkg].extras |= set(pkg_extras) - - return self.packages[pkg] + self.python3_version = Version(python3_version) + self.pypi.load_cache() def add_wheel(self, filename: str) -> None: - """Add manually a wheel to the local PyPI database. - - :param filename: path to the wheel - """ - logging.debug(f"Add wheel {filename}") - base, ext = os.path.splitext(os.path.basename(filename)) - ( - project_name, - project_version, - python_version, - abi_version, - platform, - ) = base.split("-") - project_name = project_name.lower().replace("_", "-") - - pkg = project_name.replace(".", "-") - logging.debug(f"Add metadata for {pkg}") - self.db[pkg] = { - "timestamp": time.time(), - "metadata": { - "info": {"name": project_name}, - "local": True, - "releases": { - project_version: [ - { - "packagetype": "bdist_wheel", - "filename": os.path.basename(filename), - "url": f"file://{filename}", - } - ] - }, - }, - } + """Introduce a local wheel into the closure.""" + name = os.path.basename(filename)[:-4].split("-")[0] + self.pypi.cache[canonicalize_name(name)] = [ + PyPILink( + url=f"file://{os.path.abspath(filename)}", + yanked=None, + has_metadata=False, + ) + ] - def closure(self) -> set[Package]: - """Return the full Package closure. + def add_requirement(self, req: str | Requirement) -> None: + """Add a requirement in the closure.""" + if isinstance(req, str): + req = Requirement(req) + self.requirements.add(req) - :return: a set of Package object. - """ + def file_closure(self) -> list[str]: + reqs = self._requirements_closure() result = set() - for r in self.explicit_requirements: - project_name = canonicalize_name(r.name) - result.add(self.pkg(project_name)) - result |= self.pkg(project_name).closure() - return result + for req in reqs: + has_generic_wheel = any( + ( + c + for c in self.pypi.candidate_cache[canonicalize_name(req.name)] + if c.is_generic_wheel + ) + ) - def file_closure(self) -> set[str]: - """Return the file closure. + for candidate in self.pypi.candidate_cache[canonicalize_name(req.name)]: + # Skip source files if we have a generic wheel + if has_generic_wheel and not candidate.is_wheel: + continue + + if candidate.version in req.specifier: + if candidate.is_compatible_with_cpython3( + int(self.python3_version.minor) + ) and candidate.is_compatible_with_platforms(self.platforms): + result.add(candidate) + candidate.download() + return [os.path.join(el.cache_dir, el.filename) for el in result] + + def _requirements_closure(self) -> dict: + all_reqs: dict[Requirement, set[str]] = {} + for platform in self.platforms: + provider = PyPIProvider( + get_pip_env(platform, python_version=self.python3_version), self.pypi + ) + reporter = BaseReporter() + resolver: Resolver = Resolver(provider, reporter) + try: + result = resolver.resolve(self.requirements, max_rounds=500) + except ResolutionImpossible as e: + raise PyPIError(f"Impossible resolution: {e}") from e - :return: a list of paths - """ - result = set() - for r in self.explicit_requirements: - result |= self.pkg(canonicalize_name(r.name)).file_closure() - return result + for name, candidate in result.mapping.items(): + # Skip intermediate nodes introduced to handle extras + if "@" in name: + continue - def closure_as_requirements(self) -> list[Requirement]: - """Return the list of frozen requirements for the closure. + base_req = Requirement(f"{name} == {candidate.version}") + if base_req not in all_reqs: + all_reqs[base_req] = set() + all_reqs[base_req].add(platform) - :return: a list of Requirements - """ - req_lines = set() - for p in self.closure(): - req_lines |= set(p.as_requirement) - return sorted(req_lines, key=lambda x: canonicalize_name(x.name)) - - def add_requirement(self, req: Requirement | str, explicit: bool = True) -> Package: - """Add a requirement to the closure. - - :param req: a python requirement - :param explicit: whether it is an explicit requirement or not - (False value is used only internally) - :return: a Package + return all_reqs + + def requirements_closure(self) -> list[Requirement]: + """Get the closure of requirements. + + :return: return a list of requirement that can be used as a lock file """ - if isinstance(req, str): - req = Requirement.parse(req) + all_reqs = self._requirements_closure() + reqs = [] + for k, v in all_reqs.items(): + if len(v) == len(self.platforms): + reqs.append(k) + else: + for p in v: + reqs.append(Requirement(f'{v}; sys_platform == "{p}"')) + return reqs - if explicit: - self.explicit_requirements.add(req) + def __enter__(self) -> PyPIClosure: + return self - if req not in self.requirements: - self.requirements.add(req) - logger.info( - f"Add requirement {canonicalize_name(req.name)} " f"extras={req.extras}" - ) - pkg = self.pkg(canonicalize_name(req.name), extras=list(req.extras)) - pkg.add_constraint(req) - pkg.closure() - return pkg - else: - return self.pkg(canonicalize_name(req.name), extras=list(req.extras)) - - def load_cache_file(self) -> None: - """Load cache information.""" - if self.cache_file is not None and os.path.isfile(self.cache_file): - with open(self.cache_file) as fd: - self.db = json.load(fd) - - def save_cache_file(self) -> None: - """Save cache to file.""" - if self.cache_file is not None: - with open(self.cache_file, "w") as fd: - fd.write(json.dumps(self.db, indent=2)) + def __exit__( + self, + _type: type[BaseException] | None, + _val: BaseException | None, + _tb: TracebackType | None, + ) -> None: + self.pypi.save_cache() diff --git a/src/e3/python/pypiscript.py b/src/e3/python/pypiscript.py index 28e6c51b..7262a0b4 100644 --- a/src/e3/python/pypiscript.py +++ b/src/e3/python/pypiscript.py @@ -2,7 +2,7 @@ from e3.python.pypi import PyPIClosure from e3.python.wheel import Wheel from e3.anod.checkout import CheckoutManager -from pkg_resources import Requirement +from packaging.requirements import Requirement from e3.main import Main from e3.fs import mkdir, cp from datetime import datetime @@ -164,14 +164,14 @@ def main() -> None: ) # Compute the list of toplevel requirements - toplevel_reqs = {Requirement.parse(wheel) for wheel in config.get("wheels", {})} | { - Requirement.parse(r) for r in config.get("requirements", []) + toplevel_reqs = {Requirement(wheel) for wheel in config.get("wheels", {})} | { + Requirement(r) for r in config.get("requirements", []) } with PyPIClosure( cache_file=os.path.join(m.args.cache_dir, "pip.cache"), cache_dir=wheel_cache_dir, - python3_version=m.args.python3_version, + python3_version=f"3.{m.args.python3_version}", platforms=config["platforms"], allowed_prerelease=m.args.allowed_prerelease, allowed_yanked=m.args.allowed_yanked, @@ -198,8 +198,8 @@ def main() -> None: ), "w", ) as fd: - for req in pypi.closure_as_requirements(): + for req in pypi.requirements_closure(): if "discard_from_closure" not in config or not re.search( - config["discard_from_closure"], req.project_name + config["discard_from_closure"], req.name ): fd.write(f"{str(req)}\n") diff --git a/tests/tests_e3/conftest.py b/tests/tests_e3/conftest.py index a79b33a8..abd422ec 100644 --- a/tests/tests_e3/conftest.py +++ b/tests/tests_e3/conftest.py @@ -1,13 +1,14 @@ from __future__ import annotations from os.path import abspath, dirname, join as path_join, isfile, isdir -from functools import partial -from json import loads as json_loads from typing import TYPE_CHECKING from re import compile as regex_compile from traceback import format_stack as traceback_format_stack - -from e3.fs import mkdir +import json +import shutil +import os +from e3.fs import mkdir, cp +from e3.os.fs import touch from e3.python.wheel import Wheel import pytest @@ -25,7 +26,9 @@ class PypiSimulator: PYPI_URL = "https://pypi.org" - MATCHER = regex_compile(f"{PYPI_URL}/pypi/(?P.*)/json") + PYPIHOSTED_URL = "https://files.pythonhosted.org" + SIMPLE_MATCHER = regex_compile(f"{PYPI_URL}/simple/(?P.*)/") + DOWNLOAD_MATCHER = regex_compile(f"{PYPIHOSTED_URL}/(?P.*)") DATA_DIR = path_join(dirname(abspath(__file__)), "pypi_data") def __init__(self, requests_mock: Any) -> None: @@ -61,64 +64,73 @@ def download_file( return result def get_metadata(self, request: Any, context: Any) -> dict: - m = self.MATCHER.match(request.url) + m = self.SIMPLE_MATCHER.match(request.url) if not m: context.status_code = 400 - return { - "message": "Bad Request", - "exception": "Mocked pypi received an unexpected request", - "url": request.url, - "traceback": [tmp.strip() for tmp in traceback_format_stack()], - } + return json.dumps( + { + "message": "Bad Request", + "exception": "Mocked pypi received an unexpected request", + "url": request.url, + "traceback": [tmp.strip() for tmp in traceback_format_stack()], + } + ) package = m.group("package") - path = path_join(self.DATA_DIR, "json", f"{package}.json") + path = path_join(self.DATA_DIR, "simple", f"{package}.html") if not isfile(path): context.status_code = 404 - return { - "message": "Not Found", - "exception": f"'{package}.json' file not found", - "url": request.url, - "package": package, - "traceback": [tmp.strip() for tmp in traceback_format_stack()], - } + return json.dumps( + { + "message": "Not Found", + "exception": f"'{package}.html' file not found", + "url": request.url, + "package": package, + "traceback": [tmp.strip() for tmp in traceback_format_stack()], + } + ) try: - with open(path) as json_file: - result = json_loads(json_file.read()) + with open(path) as html_file: + result = html_file.read() - if "releases" not in result: - raise Exception("Bad json metadata: 'releases' key not found") except Exception as e: context.status_code = 500 - return { - "message": "Internal Server Error", - "exception": str(e), - "url": request.url, - "package": package, - "traceback": [tmp.strip() for tmp in traceback_format_stack()], - } - - for version, data in result["releases"].items(): - for elm in data: - # Only wheel are supported - if elm["url"] not in self.mocked_download_urls or not elm[ - "url" - ].endswith(".whl"): - self.mocked_download_urls.add(elm["url"]) - self.requests_mock.get( - elm["url"], - content=partial( - self.download_file, result["info"]["name"], version - ), - ) + return json.dumps( + { + "message": "Internal Server Error", + "exception": str(e), + "url": request.url, + "package": package, + "traceback": [tmp.strip() for tmp in traceback_format_stack()], + } + ) + + context.status_code = 200 + return result + + def get_resource(self, request: Any, context: Any) -> str: + m = self.DOWNLOAD_MATCHER.match(request.url) + package = m.group("path").split("/")[-1].split("#")[0] + package_name = "-".join(package.split("-", 2)[0:2]) + metadata_file = os.path.join(self.DATA_DIR, "metadata", package_name) + mkdir(f"{package_name}/{package_name}.dist-info") + if os.path.isfile(metadata_file): + cp(metadata_file, f"{package_name}/{package_name}.dist-info/METADATA") + else: + touch(f"{package_name}/{package_name}.dist-info/METADATA") + shutil.make_archive(package, format="zip", root_dir=package_name, base_dir=".") + with open(f"{package}.zip", "rb") as fd: + result = fd.read() + cp(f"{package}.zip", "/tmp") context.status_code = 200 return result def __enter__(self): self.requests_mock.start() - self.requests_mock.get(self.MATCHER, json=self.get_metadata) + self.requests_mock.get(self.SIMPLE_MATCHER, text=self.get_metadata) + self.requests_mock.get(self.DOWNLOAD_MATCHER, content=self.get_resource) return self def __exit__(self, type_t: Any, value: Any, traceback: Any) -> None: diff --git a/tests/tests_e3/pypi_data/metadata/setuptools_scm-7.1.0 b/tests/tests_e3/pypi_data/metadata/setuptools_scm-7.1.0 new file mode 100644 index 00000000..3612126d --- /dev/null +++ b/tests/tests_e3/pypi_data/metadata/setuptools_scm-7.1.0 @@ -0,0 +1,710 @@ +Metadata-Version: 2.1 +Name: setuptools-scm +Version: 7.1.0 +Summary: the blessed package to manage your versions by scm tags +Home-page: https://github.com/pypa/setuptools_scm/ +Author: Ronny Pfannschmidt +Author-email: opensource@ronnypfannschmidt.de +License: MIT +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Version Control +Classifier: Topic :: System :: Software Distribution +Classifier: Topic :: Utilities +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: packaging (>=20.0) +Requires-Dist: setuptools +Requires-Dist: typing-extensions +Requires-Dist: tomli (>=1.0.0) ; python_version < "3.11" +Requires-Dist: importlib-metadata ; python_version < "3.8" +Provides-Extra: test +Requires-Dist: pytest (>=6.2) ; extra == 'test' +Requires-Dist: virtualenv (>20) ; extra == 'test' +Provides-Extra: toml +Requires-Dist: setuptools (>=42) ; extra == 'toml' + +setuptools_scm +============== + +``setuptools_scm`` extracts Python package versions from ``git`` or +``hg`` metadata instead of declaring them as the version argument +or in a SCM managed file. + +Additionally ``setuptools_scm`` provides setuptools with a list of +files that are managed by the SCM (i.e. it automatically adds all of +the SCM-managed files to the sdist). Unwanted files must be excluded +by discarding them via ``MANIFEST.in``. + +``setuptools_scm`` supports the following scm out of the box: + +* git +* mercurial + +.. image:: https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg + :target: https://github.com/pypa/setuptools_scm/actions + +.. image:: https://tidelift.com/badges/package/pypi/setuptools-scm + :target: https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme + + +``pyproject.toml`` usage +------------------------ + +The preferred way to configure ``setuptools_scm`` is to author +settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``. + +This feature requires Setuptools 42 or later, released in Nov, 2019. +If your project needs to support build from sdist on older versions +of Setuptools, you will need to also implement the ``setup.py usage`` +for those legacy environments. + +First, ensure that ``setuptools_scm`` is present during the project's +built step by specifying it as one of the build requirements. + +.. code:: toml + + # pyproject.toml + [build-system] + requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] + +That will be sufficient to require ``setuptools_scm`` for projects +that support PEP 518 (`pip `_ and +`pep517 `_). Many tools, +especially those that invoke ``setup.py`` for any reason, may +continue to rely on ``setup_requires``. For maximum compatibility +with those uses, consider also including a ``setup_requires`` directive +(described below in ``setup.py usage`` and ``setup.cfg``). + +To enable version inference, you need to set the version +dynamically in the ``project`` section of ``pyproject.toml``: + +.. code:: toml + + # pyproject.toml + [project] + # version = "0.0.1" # Remove any existing version parameter. + dynamic = ["version"] + +Then add this section to your ``pyproject.toml``: + +.. code:: toml + + # pyproject.toml + [tool.setuptools_scm] + +Including this section is comparable to supplying +``use_scm_version=True`` in ``setup.py``. Additionally, +include arbitrary keyword arguments in that section +to be supplied to ``get_version()``. For example: + +.. code:: toml + + # pyproject.toml + [tool.setuptools_scm] + write_to = "pkg/_version.py" + +Where ``pkg`` is the name of your package. + +If you need to confirm which version string is being generated +or debug the configuration, you can install +`setuptools-scm `_ +directly in your working environment and run: + +.. code-block:: shell + + $ python -m setuptools_scm + + # To explore other options, try: + $ python -m setuptools_scm --help + + +``setup.py`` usage (deprecated) +------------------------------- + +.. warning:: + + ``setup_requires`` has been deprecated in favor of ``pyproject.toml`` + +The following settings are considered legacy behavior and +superseded by the ``pyproject.toml`` usage, but for maximal +compatibility, projects may also supply the configuration in +this older form. + +To use ``setuptools_scm`` just modify your project's ``setup.py`` file +like this: + +* Add ``setuptools_scm`` to the ``setup_requires`` parameter. +* Add the ``use_scm_version`` parameter and set it to ``True``. + +For example: + +.. code:: python + + from setuptools import setup + setup( + ..., + use_scm_version=True, + setup_requires=['setuptools_scm'], + ..., + ) + +Arguments to ``get_version()`` (see below) may be passed as a dictionary to +``use_scm_version``. For example: + +.. code:: python + + from setuptools import setup + setup( + ..., + use_scm_version = { + "root": "..", + "relative_to": __file__, + "local_scheme": "node-and-timestamp" + }, + setup_requires=['setuptools_scm'], + ..., + ) + +You can confirm the version number locally via ``setup.py``: + +.. code-block:: shell + + $ python setup.py --version + +.. note:: + + If you see unusual version numbers for packages but ``python setup.py + --version`` reports the expected version number, ensure ``[egg_info]`` is + not defined in ``setup.cfg``. + + +``setup.cfg`` usage (deprecated) +------------------------------------ + +as ``setup_requires`` is deprecated in favour of ``pyproject.toml`` +usage in ``setup.cfg`` is considered deprecated, +please use ``pyproject.toml`` whenever possible. + + +Programmatic usage +------------------ + +In order to use ``setuptools_scm`` from code that is one directory deeper +than the project's root, you can use: + +.. code:: python + + from setuptools_scm import get_version + version = get_version(root='..', relative_to=__file__) + +See `setup.py Usage (deprecated)`_ above for how to use this within ``setup.py``. + + +Retrieving package version at runtime +------------------------------------- + +If you have opted not to hardcode the version number inside the package, +you can retrieve it at runtime from PEP-0566_ metadata using +``importlib.metadata`` from the standard library (added in Python 3.8) +or the `importlib_metadata`_ backport: + +.. code:: python + + from importlib.metadata import version, PackageNotFoundError + + try: + __version__ = version("package-name") + except PackageNotFoundError: + # package is not installed + pass + +Alternatively, you can use ``pkg_resources`` which is included in +``setuptools`` (but has a significant runtime cost): + +.. code:: python + + from pkg_resources import get_distribution, DistributionNotFound + + try: + __version__ = get_distribution("package-name").version + except DistributionNotFound: + # package is not installed + pass + +However, this does place a runtime dependency on ``setuptools`` and can add up to +a few 100ms overhead for the package import time. + +.. _PEP-0566: https://www.python.org/dev/peps/pep-0566/ +.. _importlib_metadata: https://pypi.org/project/importlib-metadata/ + + +Usage from Sphinx +----------------- + +It is discouraged to use ``setuptools_scm`` from Sphinx itself, +instead use ``importlib.metadata`` after editable/real installation: + +.. code:: python + + # contents of docs/conf.py + from importlib.metadata import version + release = version('myproject') + # for example take major/minor + version = '.'.join(release.split('.')[:2]) + +The underlying reason is, that services like *Read the Docs* sometimes change +the working directory for good reasons and using the installed metadata +prevents using needless volatile data there. + + +Usage from Docker +----------------- + +By default, docker will not copy the ``.git`` folder into your container. +Therefore, builds with version inference might fail. +Consequently, you can use the following snipped to infer the version from +the host os without copying the entire ``.git`` folder to your Dockerfile. + +.. code:: dockerfile + + RUN --mount=source=.git,target=.git,type=bind \ + pip install --no-cache-dir -e . + +However, this build step introduces a dependency to the state of your local +.git folder the build cache and triggers the long-running pip install process on every build. +To optimize build caching, one can use an environment variable to pretend a pseudo +version that is used to cache the results of the pip install process: + +.. code:: dockerfile + + FROM python + COPY pyproject.toml + ARG PSEUDO_VERSION=1 + RUN SETUPTOOLS_SCM_PRETEND_VERSION=${PSEUDO_VERSION} pip install -e .[test] + RUN --mount=source=.git,target=.git,type=bind pip install -e . + +Note that running this Dockerfile requires docker with BuildKit enabled +`[docs] `_. + +To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired +version as a build argument. Note that ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${UPPERCASED_DIST_NAME}`` +is preferred over ``SETUPTOOLS_SCM_PRETEND_VERSION``. + + +Default versioning scheme +------------------------- + +In the standard configuration ``setuptools_scm`` takes a look at three things: + +1. latest tag (with a version number) +2. the distance to this tag (e.g. number of revisions since latest tag) +3. workdir state (e.g. uncommitted changes since latest tag) + +and uses roughly the following logic to render the version: + +no distance and clean: + ``{tag}`` +distance and clean: + ``{next_version}.dev{distance}+{scm letter}{revision hash}`` +no distance and not clean: + ``{tag}+dYYYYMMDD`` +distance and not clean: + ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD`` + +The next version is calculated by adding ``1`` to the last numeric component of +the tag. + +For Git projects, the version relies on `git describe `_, +so you will see an additional ``g`` prepended to the ``{revision hash}``. + + +Semantic Versioning (SemVer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Due to the default behavior it's necessary to always include a +patch version (the ``3`` in ``1.2.3``), or else the automatic guessing +will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in +``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag +accordingly. + +.. note:: + + Future versions of ``setuptools_scm`` will switch to `SemVer + `_ by default hiding the the old behavior as an + configurable option. + + +Builtin mechanisms for obtaining version numbers +------------------------------------------------ + +1. the SCM itself (git/hg) +2. ``.hg_archival`` files (mercurial archives) +3. ``.git_archival.txt`` files (git archives, see subsection below) +4. ``PKG-INFO`` + + +Git archives +~~~~~~~~~~~~ + +Git archives are supported, but a few changes to your repository are required. + +Create a ``.git_archival.txt`` file in the root directory of your repository, +and copy-paste this into it:: + + node: $Format:%H$ + node-date: $Format:%cI$ + describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ + ref-names: $Format:%D$ + +Create the ``.gitattributes`` file in the root directory of your repository +if it doesn't already exist, and copy-paste this into it:: + + .git_archival.txt export-subst + +Finally, don't forget to commit those two files:: + + git add .git_archival.txt .gitattributes && git commit + +Note that if you are creating a ``_version.py`` file, note that it should not +be kept in version control. + + +File finders hook makes most of MANIFEST.in unnecessary +------------------------------------------------------- + +``setuptools_scm`` implements a `file_finders +`_ +entry point which returns all files tracked by your SCM. This eliminates +the need for a manually constructed ``MANIFEST.in`` in most cases where this +would be required when not using ``setuptools_scm``, namely: + +* To ensure all relevant files are packaged when running the ``sdist`` command. + +* When using `include_package_data `_ + to include package data as part of the ``build`` or ``bdist_wheel``. + +``MANIFEST.in`` may still be used: anything defined there overrides the hook. +This is mostly useful to exclude files tracked in your SCM from packages, +although in principle it can be used to explicitly include non-tracked files +too. + + +Configuration parameters +------------------------ + +In order to configure the way ``use_scm_version`` works you can provide +a mapping with options instead of a boolean value. + +The currently supported configuration keys are: + +:root: + Relative path to cwd, used for finding the SCM root; defaults to ``.`` + +:version_scheme: + Configures how the local version number is constructed; either an + entrypoint name or a callable. + +:local_scheme: + Configures how the local component of the version is constructed; either an + entrypoint name or a callable. + +:write_to: + A path to a file that gets replaced with a file containing the current + version. It is ideal for creating a ``_version.py`` file within the + package, typically used to avoid using `pkg_resources.get_distribution` + (which adds some overhead). + + .. warning:: + + Only files with :code:`.py` and :code:`.txt` extensions have builtin + templates, for other file types it is necessary to provide + :code:`write_to_template`. + +:write_to_template: + A newstyle format string that is given the current version as + the ``version`` keyword argument for formatting. + +:relative_to: + A file from which the root can be resolved. + Typically called by a script or module that is not in the root of the + repository to point ``setuptools_scm`` at the root of the repository by + supplying ``__file__``. + +:tag_regex: + A Python regex string to extract the version part from any SCM tag. + The regex needs to contain either a single match group, or a group + named ``version``, that captures the actual version information. + + Defaults to the value of ``setuptools_scm.config.DEFAULT_TAG_REGEX`` + (see `config.py `_). + +:parentdir_prefix_version: + If the normal methods for detecting the version (SCM version, + sdist metadata) fail, and the parent directory name starts with + ``parentdir_prefix_version``, then this prefix is stripped and the rest of + the parent directory name is matched with ``tag_regex`` to get a version + string. If this parameter is unset (the default), then this fallback is + not used. + + This is intended to cover GitHub's "release tarballs", which extract into + directories named ``projectname-tag/`` (in which case + ``parentdir_prefix_version`` can be set e.g. to ``projectname-``). + +:fallback_version: + A version string that will be used if no other method for detecting the + version worked (e.g., when using a tarball with no metadata). If this is + unset (the default), setuptools_scm will error if it fails to detect the + version. + +:parse: + A function that will be used instead of the discovered SCM for parsing the + version. + Use with caution, this is a function for advanced use, and you should be + familiar with the ``setuptools_scm`` internals to use it. + +:git_describe_command: + This command will be used instead the default ``git describe`` command. + Use with caution, this is a function for advanced use, and you should be + familiar with the ``setuptools_scm`` internals to use it. + + Defaults to the value set by ``setuptools_scm.git.DEFAULT_DESCRIBE`` + (see `git.py `_). + +:normalize: + A boolean flag indicating if the version string should be normalized. + Defaults to ``True``. Setting this to ``False`` is equivalent to setting + ``version_cls`` to ``setuptools_scm.version.NonNormalizedVersion`` + +:version_cls: + An optional class used to parse, verify and possibly normalize the version + string. Its constructor should receive a single string argument, and its + ``str`` should return the normalized version string to use. + This option can also receive a class qualified name as a string. + + This defaults to ``packaging.version.Version`` if available. If + ``packaging`` is not installed, ``pkg_resources.packaging.version.Version`` + is used. Note that it is known to modify git release candidate schemes. + + The ``setuptools_scm.NonNormalizedVersion`` convenience class is + provided to disable the normalization step done by + ``packaging.version.Version``. If this is used while ``setuptools_scm`` + is integrated in a setuptools packaging process, the non-normalized + version number will appear in all files (see ``write_to``) BUT note + that setuptools will still normalize it to create the final distribution, + so as to stay compliant with the python packaging standards. + +To use ``setuptools_scm`` in other Python code you can use the ``get_version`` +function: + +.. code:: python + + from setuptools_scm import get_version + my_version = get_version() + +It optionally accepts the keys of the ``use_scm_version`` parameter as +keyword arguments. + +Example configuration in ``setup.py`` format: + +.. code:: python + + from setuptools import setup + + setup( + use_scm_version={ + 'write_to': '_version.py', + 'write_to_template': '__version__ = "{version}"', + 'tag_regex': r'^(?Pv)?(?P[^\+]+)(?P.*)?$', + } + ) + + +Environment variables +--------------------- + +:SETUPTOOLS_SCM_PRETEND_VERSION: + when defined and not empty, + its used as the primary source for the version number + in which case it will be a unparsed string + +:SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${UPPERCASED_DIST_NAME}: + when defined and not empty, + its used as the primary source for the version number + in which case it will be a unparsed string + + it takes precedence over ``SETUPTOOLS_SCM_PRETEND_VERSION`` + +:SETUPTOOLS_SCM_DEBUG: + when defined and not empty, + a lot of debug information will be printed as part of ``setuptools_scm`` + operating + +:SOURCE_DATE_EPOCH: + when defined, used as the timestamp from which the + ``node-and-date`` and ``node-and-timestamp`` local parts are + derived, otherwise the current time is used + (https://reproducible-builds.org/docs/source-date-epoch/) + +:SETUPTOOLS_SCM_IGNORE_VCS_ROOTS: + when defined, a ``os.pathsep`` separated list + of directory names to ignore for root finding + + +Extending setuptools_scm +------------------------ + +``setuptools_scm`` ships with a few ``setuptools`` entrypoints based hooks to +extend its default capabilities. + + +Adding a new SCM +~~~~~~~~~~~~~~~~ + +``setuptools_scm`` provides two entrypoints for adding new SCMs: + +``setuptools_scm.parse_scm`` + A function used to parse the metadata of the current workdir + using the name of the control directory/file of your SCM as the + entrypoint's name. E.g. for the built-in entrypoint for git the + entrypoint is named ``.git`` and references ``setuptools_scm.git:parse`` + + The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance + created by the function ``setuptools_scm.version:meta``. + +``setuptools_scm.files_command`` + Either a string containing a shell command that prints all SCM managed + files in its current working directory or a callable, that given a + pathname will return that list. + + Also use then name of your SCM control directory as name of the entrypoint. + + +Version number construction +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``setuptools_scm.version_scheme`` + Configures how the version number is constructed given a + ``setuptools_scm.version.ScmVersion`` instance and should return a string + representing the version. + + Available implementations: + + :guess-next-dev: Automatically guesses the next development version (default). + Guesses the upcoming release by incrementing the pre-release segment if present, + otherwise by incrementing the micro segment. Then appends :code:`.devN`. + In case the tag ends with ``.dev0`` the version is not bumped + and custom ``.devN`` versions will trigger a error. + :post-release: generates post release versions (adds :code:`.postN`) + :python-simplified-semver: Basic semantic versioning. Guesses the upcoming release + by incrementing the minor segment and setting the micro segment to zero if the + current branch contains the string ``'feature'``, otherwise by incrementing the + micro version. Then appends :code:`.devN`. Not compatible with pre-releases. + :release-branch-semver: Semantic versioning for projects with release branches. The + same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on + a release branch: a branch whose name (ignoring namespace) parses as a version + that matches the most recent tag up to the minor segment. Otherwise if on a + non-release branch, increments the minor segment and sets the micro segment to + zero, then appends :code:`.devN`. + :no-guess-dev: Does no next version guessing, just adds :code:`.post1.devN` + +``setuptools_scm.local_scheme`` + Configures how the local part of a version is rendered given a + ``setuptools_scm.version.ScmVersion`` instance and should return a string + representing the local version. + Dates and times are in Coordinated Universal Time (UTC), because as part + of the version, they should be location independent. + + Available implementations: + + :node-and-date: adds the node on dev versions and the date on dirty + workdir (default) + :node-and-timestamp: like ``node-and-date`` but with a timestamp of + the form ``{:%Y%m%d%H%M%S}`` instead + :dirty-tag: adds ``+dirty`` if the current workdir has changes + :no-local-version: omits local version, useful e.g. because pypi does + not support it + + +Importing in ``setup.py`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To support usage in ``setup.py`` passing a callable into ``use_scm_version`` +is supported. + +Within that callable, ``setuptools_scm`` is available for import. +The callable must return the configuration. + +.. code:: python + + # content of setup.py + import setuptools + + def myversion(): + from setuptools_scm.version import get_local_dirty_tag + def clean_scheme(version): + return get_local_dirty_tag(version) if version.dirty else '+clean' + + return {'local_scheme': clean_scheme} + + setup( + ..., + use_scm_version=myversion, + ... + ) + + +Note on testing non-installed versions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While the general advice is to test against a installed version, +some environments require a test prior to install, + +.. code:: + + $ python setup.py egg_info + $ PYTHONPATH=$PWD:$PWD/src pytest + + +Interaction with Enterprise Distributions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some enterprise distributions like RHEL7 and others +ship rather old setuptools versions due to various release management details. + +In those case its typically possible to build by using a sdist against ``setuptools_scm<2.0``. +As those old setuptools versions lack sensible types for versions, +modern setuptools_scm is unable to support them sensibly. + +In case the project you need to build can not be patched to either use old setuptools_scm, +its still possible to install a more recent version of setuptools in order to handle the build +and/or install the package by using wheels or eggs. + + +Code of Conduct +--------------- + +Everyone interacting in the ``setuptools_scm`` project's codebases, issue +trackers, chat rooms, and mailing lists is expected to follow the +`PSF Code of Conduct`_. + +.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. diff --git a/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.0.0 b/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.0.0 new file mode 100644 index 00000000..e75b58a7 --- /dev/null +++ b/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.0.0 @@ -0,0 +1,160 @@ +Metadata-Version: 2.1 +Name: setuptools-scm +Version: 8.0.0 +Summary: the blessed package to manage your versions by scm tags +Author-email: Ronny Pfannschmidt +License: Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +Project-URL: repository, https://github.com/pypa/setuptools_scm/ +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Version Control +Classifier: Topic :: System :: Software Distribution +Classifier: Topic :: Utilities +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: packaging >=20 +Requires-Dist: setuptools +Requires-Dist: importlib-metadata >=4.6 ; python_version < "3.10" +Requires-Dist: tomli >=1 ; python_version < "3.11" +Requires-Dist: typing-extensions ; python_version < "3.11" +Provides-Extra: docs +Requires-Dist: entangled-cli[rich] ; extra == 'docs' +Requires-Dist: mkdocs ; extra == 'docs' +Requires-Dist: mkdocs-entangled-plugin ; extra == 'docs' +Requires-Dist: mkdocs-material ; extra == 'docs' +Requires-Dist: mkdocstrings[python] ; extra == 'docs' +Requires-Dist: pygments ; extra == 'docs' +Provides-Extra: rich +Requires-Dist: rich ; extra == 'rich' +Provides-Extra: test +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: rich ; extra == 'test' +Requires-Dist: virtualenv >20 ; extra == 'test' +Provides-Extra: toml + +# setuptools_scm +[![github ci](https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg)](https://github.com/pypa/setuptools_scm/actions) +[![tidelift](https://tidelift.com/badges/package/pypi/setuptools-scm) ](https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme) + +## about + +[setuptools-scm] extracts Python package versions from `git` or +`hg` metadata instead of declaring them as the version argument +or in an SCM managed file. + +Additionally, [setuptools-scm] provides setuptools +with a list of files that are managed by the SCM
+(i.e. it automatically adds **all of** the SCM-managed files to the sdist).
+Unwanted files must be excluded via `MANIFEST.in`. + + +## `pyproject.toml` usage + +The preferred way to configure [setuptools-scm] is to author +settings in a `tool.setuptools_scm` section of `pyproject.toml`. + +This feature requires setuptools 60 or later. +First, ensure that [setuptools-scm] is present during the project's +build step by specifying it as one of the build requirements. + +```toml +[build-system] +requires = [ + "setuptools>=60", + "setuptools-scm>=8.0"] +``` + +That will be sufficient to require [setuptools-scm] for projects +that support [PEP 518] like [pip] and [build]. + +[pip]: https://pypi.org/project/pip +[build]: https://pypi.org/project/build +[PEP 518]: https://peps.python.org/pep-0518/ + + +To enable version inference, you need to set the version +dynamically in the `project` section of `pyproject.toml`: + +```toml title="pyproject.toml" +[project] +# version = "0.0.1" # Remove any existing version parameter. +dynamic = ["version"] +[tool.setuptools_scm] +``` + +Additionally, a version file can be written by specifying: + +```toml title="pyproject.toml" +[tool.setuptools_scm] +version_file = "pkg/_version.py" +``` + +Where `pkg` is the name of your package. + +If you need to confirm which version string is being generated or debug the configuration, +you can install [setuptools-scm] directly in your working environment and run: + +[setuptools-scm]: https://github.com/pypa/setuptools_scm + +```console +$ python -m setuptools_scm +# To explore other options, try: +$ python -m setuptools_scm --help +``` + + + +## Interaction with Enterprise Distributions + +Some enterprise distributions like RHEL7 +ship rather old setuptools versions. + +In those cases its typically possible to build by using an sdist against `setuptools_scm<2.0`. +As those old setuptools versions lack sensible types for versions, +modern [setuptools-scm] is unable to support them sensibly. + +It's strongly recommended to build a wheel artifact using modern Python and setuptools, +then installing the artifact instead of trying to run against old setuptools versions. + + +## Code of Conduct + + +Everyone interacting in the [setuptools-scm] project's codebases, issue +trackers, chat rooms, and mailing lists is expected to follow the +[PSF Code of Conduct]. + +[PSF Code of Conduct]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + + +## Security Contact + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.1.0 b/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.1.0 new file mode 100644 index 00000000..edf8a1d4 --- /dev/null +++ b/tests/tests_e3/pypi_data/metadata/setuptools_scm-8.1.0 @@ -0,0 +1,166 @@ +Metadata-Version: 2.1 +Name: setuptools-scm +Version: 8.1.0 +Summary: the blessed package to manage your versions by scm tags +Author-email: Ronny Pfannschmidt +License: Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +Project-URL: documentation, https://setuptools-scm.readthedocs.io/ +Project-URL: repository, https://github.com/pypa/setuptools_scm/ +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Version Control +Classifier: Topic :: System :: Software Distribution +Classifier: Topic :: Utilities +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: packaging >=20 +Requires-Dist: setuptools +Requires-Dist: typing-extensions ; python_version < "3.10" +Requires-Dist: tomli >=1 ; python_version < "3.11" +Provides-Extra: docs +Requires-Dist: entangled-cli ~=2.0 ; extra == 'docs' +Requires-Dist: mkdocs ; extra == 'docs' +Requires-Dist: mkdocs-entangled-plugin ; extra == 'docs' +Requires-Dist: mkdocs-material ; extra == 'docs' +Requires-Dist: mkdocstrings[python] ; extra == 'docs' +Requires-Dist: pygments ; extra == 'docs' +Provides-Extra: rich +Requires-Dist: rich ; extra == 'rich' +Provides-Extra: test +Requires-Dist: build ; extra == 'test' +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: rich ; extra == 'test' +Requires-Dist: wheel ; extra == 'test' +Requires-Dist: typing-extensions ; (python_version < "3.11") and extra == 'test' +Provides-Extra: toml + +# setuptools_scm +[![github ci](https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg)](https://github.com/pypa/setuptools_scm/actions) +[![Documentation Status](https://readthedocs.org/projects/setuptools-scm/badge/?version=latest)](https://setuptools-scm.readthedocs.io/en/latest/?badge=latest) +[![tidelift](https://tidelift.com/badges/package/pypi/setuptools-scm) ](https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme) + +## about + +[setuptools-scm] extracts Python package versions from `git` or +`hg` metadata instead of declaring them as the version argument +or in an SCM managed file. + +Additionally, [setuptools-scm] provides setuptools +with a list of files that are managed by the SCM
+(i.e. it automatically adds **all of** the SCM-managed files to the sdist).
+Unwanted files must be excluded via `MANIFEST.in`. + + +## `pyproject.toml` usage + +The preferred way to configure [setuptools-scm] is to author +settings in a `tool.setuptools_scm` section of `pyproject.toml`. + +This feature requires setuptools 61 or later. +First, ensure that [setuptools-scm] is present during the project's +build step by specifying it as one of the build requirements. + +```toml title="pyproject.toml" +[build-system] +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" +``` + +That will be sufficient to require [setuptools-scm] for projects +that support [PEP 518] like [pip] and [build]. + +[pip]: https://pypi.org/project/pip +[build]: https://pypi.org/project/build +[PEP 518]: https://peps.python.org/pep-0518/ + + +To enable version inference, you need to set the version +dynamically in the `project` section of `pyproject.toml`: + +```toml title="pyproject.toml" +[project] +# version = "0.0.1" # Remove any existing version parameter. +dynamic = ["version"] + +[tool.setuptools_scm] +``` + +Additionally, a version file can be written by specifying: + +```toml title="pyproject.toml" +[tool.setuptools_scm] +version_file = "pkg/_version.py" +``` + +Where `pkg` is the name of your package. + +If you need to confirm which version string is being generated or debug the configuration, +you can install [setuptools-scm] directly in your working environment and run: + +```console +$ python -m setuptools_scm +# To explore other options, try: +$ python -m setuptools_scm --help +``` + +For further configuration see the [documentation]. + +[setuptools-scm]: https://github.com/pypa/setuptools_scm +[documentation]: https://setuptools-scm.readthedocs.io/ + + +## Interaction with Enterprise Distributions + +Some enterprise distributions like RHEL7 +ship rather old setuptools versions. + +In those cases its typically possible to build by using an sdist against `setuptools_scm<2.0`. +As those old setuptools versions lack sensible types for versions, +modern [setuptools-scm] is unable to support them sensibly. + +It's strongly recommended to build a wheel artifact using modern Python and setuptools, +then installing the artifact instead of trying to run against old setuptools versions. + + +## Code of Conduct + + +Everyone interacting in the [setuptools-scm] project's codebases, issue +trackers, chat rooms, and mailing lists is expected to follow the +[PSF Code of Conduct]. + +[PSF Code of Conduct]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + + +## Security Contact + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/tests/tests_e3/pypi_data/simple/packaging.html b/tests/tests_e3/pypi_data/simple/packaging.html new file mode 100644 index 00000000..5659d455 --- /dev/null +++ b/tests/tests_e3/pypi_data/simple/packaging.html @@ -0,0 +1,101 @@ + + + + + Links for packaging + + +

Links for packaging

+packaging-14.0-py2.py3-none-any.whl
+packaging-14.0.tar.gz
+packaging-14.1-py2.py3-none-any.whl
+packaging-14.1.tar.gz
+packaging-14.2-py2.py3-none-any.whl
+packaging-14.2.tar.gz
+packaging-14.3-py2.py3-none-any.whl
+packaging-14.3.tar.gz
+packaging-14.4-py2.py3-none-any.whl
+packaging-14.4.tar.gz
+packaging-14.5-py2.py3-none-any.whl
+packaging-14.5.tar.gz
+packaging-15.0-py2.py3-none-any.whl
+packaging-15.0.tar.gz
+packaging-15.1-py2.py3-none-any.whl
+packaging-15.1.tar.gz
+packaging-15.2-py2.py3-none-any.whl
+packaging-15.2.tar.gz
+packaging-15.3-py2.py3-none-any.whl
+packaging-15.3.tar.gz
+packaging-16.0-py2.py3-none-any.whl
+packaging-16.0.tar.gz
+packaging-16.1-py2.py3-none-any.whl
+packaging-16.1.tar.gz
+packaging-16.2-py2.py3-none-any.whl
+packaging-16.2.tar.gz
+packaging-16.3-py2.py3-none-any.whl
+packaging-16.3.tar.gz
+packaging-16.4-py2.py3-none-any.whl
+packaging-16.4.tar.gz
+packaging-16.5-py2.py3-none-any.whl
+packaging-16.5.tar.gz
+packaging-16.6-py2.py3-none-any.whl
+packaging-16.6.tar.gz
+packaging-16.7-py2.py3-none-any.whl
+packaging-16.7.tar.gz
+packaging-16.8-py2.py3-none-any.whl
+packaging-16.8.tar.gz
+packaging-17.0-py2.py3-none-any.whl
+packaging-17.0.tar.gz
+packaging-17.1-py2.py3-none-any.whl
+packaging-17.1.tar.gz
+packaging-18.0-py2.py3-none-any.whl
+packaging-18.0.tar.gz
+packaging-19.0-py2.py3-none-any.whl
+packaging-19.0.tar.gz
+packaging-19.1-py2.py3-none-any.whl
+packaging-19.1.tar.gz
+packaging-19.2-py2.py3-none-any.whl
+packaging-19.2.tar.gz
+packaging-20.0-py2.py3-none-any.whl
+packaging-20.0.tar.gz
+packaging-20.1-py2.py3-none-any.whl
+packaging-20.1.tar.gz
+packaging-20.2-py2.py3-none-any.whl
+packaging-20.2.tar.gz
+packaging-20.3-py2.py3-none-any.whl
+packaging-20.3.tar.gz
+packaging-20.4-py2.py3-none-any.whl
+packaging-20.4.tar.gz
+packaging-20.5-py2.py3-none-any.whl
+packaging-20.5.tar.gz
+packaging-20.6-py2.py3-none-any.whl
+packaging-20.6.tar.gz
+packaging-20.7-py2.py3-none-any.whl
+packaging-20.7.tar.gz
+packaging-20.8-py2.py3-none-any.whl
+packaging-20.8.tar.gz
+packaging-20.9-py2.py3-none-any.whl
+packaging-20.9.tar.gz
+packaging-21.0-py3-none-any.whl
+packaging-21.0.tar.gz
+packaging-21.1-py3-none-any.whl
+packaging-21.1.tar.gz
+packaging-21.2-py3-none-any.whl
+packaging-21.2.tar.gz
+packaging-21.3-py3-none-any.whl
+packaging-21.3.tar.gz
+packaging-22.0-py3-none-any.whl
+packaging-22.0.tar.gz
+packaging-23.0-py3-none-any.whl
+packaging-23.0.tar.gz
+packaging-23.1-py3-none-any.whl
+packaging-23.1.tar.gz
+packaging-23.2-py3-none-any.whl
+packaging-23.2.tar.gz
+packaging-24.0-py3-none-any.whl
+packaging-24.0.tar.gz
+packaging-24.1-py3-none-any.whl
+packaging-24.1.tar.gz
+ + + \ No newline at end of file diff --git a/tests/tests_e3/pypi_data/simple/setuptools-scm.html b/tests/tests_e3/pypi_data/simple/setuptools-scm.html new file mode 100644 index 00000000..59b9b8b0 --- /dev/null +++ b/tests/tests_e3/pypi_data/simple/setuptools-scm.html @@ -0,0 +1,370 @@ + + + + + Links for setuptools-scm + + +

Links for setuptools-scm

+setuptools-scm-1.0.0.tar.gz
+setuptools_scm-1.0.0-py2.py3-none-any.whl
+setuptools-scm-1.1.0.tar.gz
+setuptools_scm-1.1.0-py2.py3-none-any.whl
+setuptools-scm-1.2.0.tar.gz
+setuptools_scm-1.2.0-py2.py3-none-any.whl
+setuptools-scm-1.3.0.tar.gz
+setuptools_scm-1.3.0-py2.py3-none-any.whl
+setuptools-scm-1.4.0.tar.gz
+setuptools_scm-1.4.0-py2.py3-none-any.whl
+setuptools_scm-1.4.1-py2.py3-none-any.whl
+setuptools_scm-1.4.1.tar.gz
+setuptools_scm-1.5.0-py2.py3-none-any.whl
+setuptools_scm-1.5.0.tar.gz
+setuptools_scm-1.5.2-py2.py3-none-any.whl
+setuptools_scm-1.5.2.tar.gz
+setuptools_scm-1.5.3.tar.gz
+setuptools_scm-1.5.4.tar.gz
+setuptools_scm-1.5.5.tar.gz
+setuptools_scm-1.6.0-py2.py3-none-any.whl
+setuptools_scm-1.6.0.tar.gz
+setuptools_scm-1.7.0-py2.py3-none-any.whl
+setuptools_scm-1.7.0.tar.gz
+setuptools_scm-1.8.0-py2.py3-none-any.whl
+setuptools_scm-1.8.0.tar.bz2
+setuptools_scm-1.8.0.zip
+setuptools_scm-1.9.0-py2.py3-none-any.whl
+setuptools_scm-1.9.0.tar.gz
+setuptools_scm-1.10.0-py2.py3-none-any.whl
+setuptools_scm-1.10.0.tar.bz2
+setuptools_scm-1.10.0.zip
+setuptools_scm-1.10.1-py2.py3-none-any.whl
+setuptools_scm-1.10.1.tar.bz2
+setuptools_scm-1.10.1.zip
+setuptools_scm-1.11.0-py2.py3-none-any.whl
+setuptools_scm-1.11.0.tar.gz
+setuptools_scm-1.11.1-py2.py3-none-any.whl
+setuptools_scm-1.11.1.tar.gz
+setuptools_scm-1.13.0-py2.py3-none-any.whl
+setuptools_scm-1.13.0.tar.gz
+setuptools_scm-1.13.1-py2.py3-none-any.whl
+setuptools_scm-1.13.1.tar.gz
+setuptools_scm-1.14.0rc1-py2.6.egg
+setuptools_scm-1.14.0rc1-py2.7.egg
+setuptools_scm-1.14.0rc1-py2.py3-none-any.whl
+setuptools_scm-1.14.0rc1-py3.3.egg
+setuptools_scm-1.14.0rc1-py3.4.egg
+setuptools_scm-1.14.0rc1-py3.5.egg
+setuptools_scm-1.14.0rc1.tar.gz
+setuptools_scm-1.14.0-py2.6.egg
+setuptools_scm-1.14.0-py2.7.egg
+setuptools_scm-1.14.0-py2.py3-none-any.whl
+setuptools_scm-1.14.0-py3.3.egg
+setuptools_scm-1.14.0-py3.4.egg
+setuptools_scm-1.14.0-py3.5.egg
+setuptools_scm-1.14.0.tar.gz
+setuptools_scm-1.15.0rc1-py2.6.egg
+setuptools_scm-1.15.0rc1-py2.7.egg
+setuptools_scm-1.15.0rc1-py2.py3-none-any.whl
+setuptools_scm-1.15.0rc1-py3.3.egg
+setuptools_scm-1.15.0rc1-py3.4.egg
+setuptools_scm-1.15.0rc1-py3.5.egg
+setuptools_scm-1.15.0rc1.tar.gz
+setuptools_scm-1.15.0-py2.6.egg
+setuptools_scm-1.15.0-py2.7.egg
+setuptools_scm-1.15.0-py2.py3-none-any.whl
+setuptools_scm-1.15.0-py3.3.egg
+setuptools_scm-1.15.0-py3.4.egg
+setuptools_scm-1.15.0-py3.5.egg
+setuptools_scm-1.15.0-py3.6.egg
+setuptools_scm-1.15.0.tar.gz
+setuptools_scm-1.15.1rc1-py2.6.egg
+setuptools_scm-1.15.1rc1-py2.7.egg
+setuptools_scm-1.15.1rc1-py2.py3-none-any.whl
+setuptools_scm-1.15.1rc1-py3.3.egg
+setuptools_scm-1.15.1rc1-py3.4.egg
+setuptools_scm-1.15.1rc1-py3.5.egg
+setuptools_scm-1.15.1rc1-py3.6.egg
+setuptools_scm-1.15.1rc1.tar.gz
+setuptools_scm-1.15.4-py2.6.egg
+setuptools_scm-1.15.4-py2.7.egg
+setuptools_scm-1.15.4-py2.py3-none-any.whl
+setuptools_scm-1.15.4-py3.3.egg
+setuptools_scm-1.15.4-py3.4.egg
+setuptools_scm-1.15.4-py3.5.egg
+setuptools_scm-1.15.4-py3.6.egg
+setuptools_scm-1.15.4.tar.gz
+setuptools_scm-1.15.5-py2.6.egg
+setuptools_scm-1.15.5-py2.7.egg
+setuptools_scm-1.15.5-py2.py3-none-any.whl
+setuptools_scm-1.15.5-py3.3.egg
+setuptools_scm-1.15.5-py3.4.egg
+setuptools_scm-1.15.5-py3.5.egg
+setuptools_scm-1.15.5-py3.6.egg
+setuptools_scm-1.15.5.tar.gz
+setuptools_scm-1.15.6-py2.6.egg
+setuptools_scm-1.15.6-py2.7.egg
+setuptools_scm-1.15.6-py2.py3-none-any.whl
+setuptools_scm-1.15.6-py3.3.egg
+setuptools_scm-1.15.6-py3.4.egg
+setuptools_scm-1.15.6-py3.5.egg
+setuptools_scm-1.15.6-py3.6.egg
+setuptools_scm-1.15.6.tar.gz
+setuptools_scm-1.15.7-py2.7.egg
+setuptools_scm-1.15.7-py2.py3-none-any.whl
+setuptools_scm-1.15.7-py3.3.egg
+setuptools_scm-1.15.7-py3.4.egg
+setuptools_scm-1.15.7-py3.5.egg
+setuptools_scm-1.15.7-py3.6.egg
+setuptools_scm-1.15.7.tar.gz
+setuptools_scm-1.16.0-py2.7.egg
+setuptools_scm-1.16.0-py2.py3-none-any.whl
+setuptools_scm-1.16.0-py3.4.egg
+setuptools_scm-1.16.0-py3.5.egg
+setuptools_scm-1.16.0-py3.6.egg
+setuptools_scm-1.16.0.tar.gz
+setuptools_scm-1.16.1-py2.7.egg
+setuptools_scm-1.16.1-py2.py3-none-any.whl
+setuptools_scm-1.16.1-py3.4.egg
+setuptools_scm-1.16.1-py3.5.egg
+setuptools_scm-1.16.1-py3.6.egg
+setuptools_scm-1.16.1.tar.gz
+setuptools_scm-1.16.2-py2.7.egg
+setuptools_scm-1.16.2-py2.py3-none-any.whl
+setuptools_scm-1.16.2-py3.4.egg
+setuptools_scm-1.16.2-py3.5.egg
+setuptools_scm-1.16.2-py3.6.egg
+setuptools_scm-1.16.2.tar.gz
+setuptools_scm-1.17.0-py2.7.egg
+setuptools_scm-1.17.0-py2.py3-none-any.whl
+setuptools_scm-1.17.0-py3.4.egg
+setuptools_scm-1.17.0-py3.5.egg
+setuptools_scm-1.17.0-py3.6.egg
+setuptools_scm-1.17.0.tar.gz
+setuptools_scm-2.0.0-py2.7.egg
+setuptools_scm-2.0.0-py2.py3-none-any.whl
+setuptools_scm-2.0.0-py3.4.egg
+setuptools_scm-2.0.0-py3.5.egg
+setuptools_scm-2.0.0-py3.6.egg
+setuptools_scm-2.0.0.tar.gz
+setuptools_scm-2.1.0-py2.7.egg
+setuptools_scm-2.1.0-py2.py3-none-any.whl
+setuptools_scm-2.1.0-py3.4.egg
+setuptools_scm-2.1.0-py3.5.egg
+setuptools_scm-2.1.0-py3.6.egg
+setuptools_scm-2.1.0.tar.gz
+setuptools_scm-3.0.0-py2.7.egg
+setuptools_scm-3.0.0-py2.py3-none-any.whl
+setuptools_scm-3.0.0-py3.4.egg
+setuptools_scm-3.0.0-py3.5.egg
+setuptools_scm-3.0.0-py3.6.egg
+setuptools_scm-3.0.0.tar.gz
+setuptools_scm-3.0.1-py2.7.egg
+setuptools_scm-3.0.1-py2.py3-none-any.whl
+setuptools_scm-3.0.1-py3.4.egg
+setuptools_scm-3.0.1-py3.5.egg
+setuptools_scm-3.0.1-py3.6.egg
+setuptools_scm-3.0.1.tar.gz
+setuptools_scm-3.0.2-py2.7.egg
+setuptools_scm-3.0.2-py2.py3-none-any.whl
+setuptools_scm-3.0.2-py3.4.egg
+setuptools_scm-3.0.2-py3.5.egg
+setuptools_scm-3.0.2-py3.6.egg
+setuptools_scm-3.0.2.tar.gz
+setuptools_scm-3.0.4-py2.7.egg
+setuptools_scm-3.0.4-py2.py3-none-any.whl
+setuptools_scm-3.0.4-py3.4.egg
+setuptools_scm-3.0.4-py3.5.egg
+setuptools_scm-3.0.4-py3.6.egg
+setuptools_scm-3.0.4.tar.gz
+setuptools_scm-3.0.5-py2.7.egg
+setuptools_scm-3.0.5-py2.py3-none-any.whl
+setuptools_scm-3.0.5-py3.4.egg
+setuptools_scm-3.0.5-py3.5.egg
+setuptools_scm-3.0.5-py3.6.egg
+setuptools_scm-3.0.5.tar.gz
+setuptools_scm-3.0.6-py2.7.egg
+setuptools_scm-3.0.6-py2.py3-none-any.whl
+setuptools_scm-3.0.6-py3.4.egg
+setuptools_scm-3.0.6-py3.5.egg
+setuptools_scm-3.0.6-py3.6.egg
+setuptools_scm-3.0.6.tar.gz
+setuptools_scm-3.1.0-py2.7.egg
+setuptools_scm-3.1.0-py2.py3-none-any.whl
+setuptools_scm-3.1.0-py3.4.egg
+setuptools_scm-3.1.0-py3.5.egg
+setuptools_scm-3.1.0-py3.6.egg
+setuptools_scm-3.1.0.tar.gz
+setuptools_scm-3.2.0-py2.7.egg
+setuptools_scm-3.2.0-py2.py3-none-any.whl
+setuptools_scm-3.2.0-py3.4.egg
+setuptools_scm-3.2.0-py3.5.egg
+setuptools_scm-3.2.0-py3.6.egg
+setuptools_scm-3.2.0.tar.gz
+setuptools_scm-3.3.1-py2.7.egg
+setuptools_scm-3.3.1-py2.py3-none-any.whl
+setuptools_scm-3.3.1-py3.4.egg
+setuptools_scm-3.3.1-py3.5.egg
+setuptools_scm-3.3.1-py3.6.egg
+setuptools_scm-3.3.1.tar.gz
+setuptools_scm-3.3.2-py2.7.egg
+setuptools_scm-3.3.2-py2.py3-none-any.whl
+setuptools_scm-3.3.2-py3.4.egg
+setuptools_scm-3.3.2-py3.5.egg
+setuptools_scm-3.3.2-py3.6.egg
+setuptools_scm-3.3.2.tar.gz
+setuptools_scm-3.3.3-py2.7.egg
+setuptools_scm-3.3.3-py2.py3-none-any.whl
+setuptools_scm-3.3.3-py3.4.egg
+setuptools_scm-3.3.3-py3.5.egg
+setuptools_scm-3.3.3-py3.6.egg
+setuptools_scm-3.3.3-py3.7.egg
+setuptools_scm-3.3.3-py3.8.egg
+setuptools_scm-3.3.3.tar.gz
+setuptools_scm-3.4.0-py2.7.egg
+setuptools_scm-3.4.0-py2.py3-none-any.whl
+setuptools_scm-3.4.0-py3.4.egg
+setuptools_scm-3.4.0-py3.5.egg
+setuptools_scm-3.4.0-py3.6.egg
+setuptools_scm-3.4.0-py3.7.egg
+setuptools_scm-3.4.0-py3.8.egg
+setuptools_scm-3.4.0.tar.gz
+setuptools_scm-3.4.1-py2.7.egg
+setuptools_scm-3.4.1-py2.py3-none-any.whl
+setuptools_scm-3.4.1-py3.4.egg
+setuptools_scm-3.4.1-py3.5.egg
+setuptools_scm-3.4.1-py3.6.egg
+setuptools_scm-3.4.1-py3.7.egg
+setuptools_scm-3.4.1-py3.8.egg
+setuptools_scm-3.4.1.tar.gz
+setuptools_scm-3.4.2-py2.7.egg
+setuptools_scm-3.4.2-py2.py3-none-any.whl
+setuptools_scm-3.4.2-py3.4.egg
+setuptools_scm-3.4.2-py3.5.egg
+setuptools_scm-3.4.2-py3.6.egg
+setuptools_scm-3.4.2-py3.7.egg
+setuptools_scm-3.4.2-py3.8.egg
+setuptools_scm-3.4.2.tar.gz
+setuptools_scm-3.4.3-py2.7.egg
+setuptools_scm-3.4.3-py2.py3-none-any.whl
+setuptools_scm-3.4.3-py3.4.egg
+setuptools_scm-3.4.3-py3.5.egg
+setuptools_scm-3.4.3-py3.6.egg
+setuptools_scm-3.4.3-py3.7.egg
+setuptools_scm-3.4.3-py3.8.egg
+setuptools_scm-3.4.3.tar.gz
+setuptools_scm-3.5.0-py2.7.egg
+setuptools_scm-3.5.0-py2.py3-none-any.whl
+setuptools_scm-3.5.0-py3.5.egg
+setuptools_scm-3.5.0-py3.6.egg
+setuptools_scm-3.5.0-py3.7.egg
+setuptools_scm-3.5.0-py3.8.egg
+setuptools_scm-3.5.0.tar.gz
+setuptools_scm-4.0.0-py2.7.egg
+setuptools_scm-4.0.0-py2.py3-none-any.whl
+setuptools_scm-4.0.0-py3-none-any.whl
+setuptools_scm-4.0.0-py3.5.egg
+setuptools_scm-4.0.0-py3.6.egg
+setuptools_scm-4.0.0-py3.7.egg
+setuptools_scm-4.0.0-py3.8.egg
+setuptools_scm-4.0.0.tar.gz
+setuptools_scm-4.1.0-py2.7.egg
+setuptools_scm-4.1.0-py2.py3-none-any.whl
+setuptools_scm-4.1.0-py3.5.egg
+setuptools_scm-4.1.0-py3.6.egg
+setuptools_scm-4.1.0-py3.7.egg
+setuptools_scm-4.1.0-py3.8.egg
+setuptools_scm-4.1.0-py3.9.egg
+setuptools_scm-4.1.0.tar.gz
+setuptools_scm-4.1.1-py2.7.egg
+setuptools_scm-4.1.1-py2.py3-none-any.whl
+setuptools_scm-4.1.1-py3.5.egg
+setuptools_scm-4.1.1-py3.6.egg
+setuptools_scm-4.1.1-py3.7.egg
+setuptools_scm-4.1.1-py3.8.egg
+setuptools_scm-4.1.1-py3.9.egg
+setuptools_scm-4.1.1.tar.gz
+setuptools_scm-4.1.2-py2.7.egg
+setuptools_scm-4.1.2-py2.py3-none-any.whl
+setuptools_scm-4.1.2-py3.5.egg
+setuptools_scm-4.1.2-py3.6.egg
+setuptools_scm-4.1.2-py3.7.egg
+setuptools_scm-4.1.2-py3.8.egg
+setuptools_scm-4.1.2-py3.9.egg
+setuptools_scm-4.1.2.tar.gz
+setuptools_scm-5.0.0-py2.7.egg
+setuptools_scm-5.0.0-py2.py3-none-any.whl
+setuptools_scm-5.0.0-py3.5.egg
+setuptools_scm-5.0.0-py3.6.egg
+setuptools_scm-5.0.0-py3.7.egg
+setuptools_scm-5.0.0-py3.8.egg
+setuptools_scm-5.0.0-py3.9.egg
+setuptools_scm-5.0.0.tar.gz
+setuptools_scm-5.0.1-py2.7.egg
+setuptools_scm-5.0.1-py2.py3-none-any.whl
+setuptools_scm-5.0.1-py3.5.egg
+setuptools_scm-5.0.1-py3.6.egg
+setuptools_scm-5.0.1-py3.7.egg
+setuptools_scm-5.0.1-py3.8.egg
+setuptools_scm-5.0.1-py3.9.egg
+setuptools_scm-5.0.1.tar.gz
+setuptools_scm-5.0.2-py2.7.egg
+setuptools_scm-5.0.2-py2.py3-none-any.whl
+setuptools_scm-5.0.2-py3.5.egg
+setuptools_scm-5.0.2-py3.6.egg
+setuptools_scm-5.0.2-py3.7.egg
+setuptools_scm-5.0.2-py3.8.egg
+setuptools_scm-5.0.2-py3.9.egg
+setuptools_scm-5.0.2.tar.gz
+setuptools_scm-6.0.0-py3-none-any.whl
+setuptools_scm-6.0.0.tar.gz
+setuptools_scm-6.0.1-py3-none-any.whl
+setuptools_scm-6.0.1.tar.gz
+setuptools_scm-6.1.0.dev0-py3-none-any.whl
+setuptools_scm-6.1.0.dev0.tar.gz
+setuptools_scm-6.1.0-py3-none-any.whl
+setuptools_scm-6.1.0.tar.gz
+setuptools_scm-6.1.1-py3-none-any.whl
+setuptools_scm-6.1.1.tar.gz
+setuptools_scm-6.2.0-py3-none-any.whl
+setuptools_scm-6.2.0.tar.gz
+setuptools_scm-6.3.0-py3-none-any.whl
+setuptools_scm-6.3.0.tar.gz
+setuptools_scm-6.3.1-py3-none-any.whl
+setuptools_scm-6.3.1.tar.gz
+setuptools_scm-6.3.2-py3-none-any.whl
+setuptools_scm-6.3.2.tar.gz
+setuptools_scm-6.4.0-py3-none-any.whl
+setuptools_scm-6.4.0.tar.gz
+setuptools_scm-6.4.1-py3-none-any.whl
+setuptools_scm-6.4.1.tar.gz
+setuptools_scm-6.4.2-py3-none-any.whl
+setuptools_scm-6.4.2.tar.gz
+setuptools_scm-7.0.0-py3-none-any.whl
+setuptools_scm-7.0.0.tar.gz
+setuptools_scm-7.0.1-py3-none-any.whl
+setuptools_scm-7.0.1.tar.gz
+setuptools_scm-7.0.2-py3-none-any.whl
+setuptools_scm-7.0.2.tar.gz
+setuptools_scm-7.0.3-py3-none-any.whl
+setuptools_scm-7.0.3.tar.gz
+setuptools_scm-7.0.4-py3-none-any.whl
+setuptools_scm-7.0.4.tar.gz
+setuptools_scm-7.0.5-py3-none-any.whl
+setuptools_scm-7.0.5.tar.gz
+setuptools_scm-7.1.0-py3-none-any.whl
+setuptools_scm-7.1.0.tar.gz
+setuptools-scm-8.0.0.tar.gz
+setuptools_scm-8.0.0-py3-none-any.whl
+setuptools-scm-8.0.1.tar.gz
+setuptools_scm-8.0.1-py3-none-any.whl
+setuptools-scm-8.0.2.tar.gz
+setuptools_scm-8.0.2-py3-none-any.whl
+setuptools-scm-8.0.3.tar.gz
+setuptools_scm-8.0.3-py3-none-any.whl
+setuptools-scm-8.0.4.tar.gz
+setuptools_scm-8.0.4-py3-none-any.whl
+setuptools_scm-8.1.0-py3-none-any.whl
+setuptools_scm-8.1.0.tar.gz
+ + + \ No newline at end of file diff --git a/tests/tests_e3/pypi_data/simple/setuptools.html b/tests/tests_e3/pypi_data/simple/setuptools.html new file mode 100644 index 00000000..400a55d2 --- /dev/null +++ b/tests/tests_e3/pypi_data/simple/setuptools.html @@ -0,0 +1,1433 @@ + + + + + Links for setuptools + + +

Links for setuptools

+setuptools-0.6b1-py2.3.egg
+setuptools-0.6b1-py2.4.egg
+setuptools-0.6b1.zip
+setuptools-0.6b2-py2.3.egg
+setuptools-0.6b2-py2.4.egg
+setuptools-0.6b2.zip
+setuptools-0.6b3-py2.3.egg
+setuptools-0.6b3-py2.4.egg
+setuptools-0.6b3.zip
+setuptools-0.6b4-py2.3.egg
+setuptools-0.6b4-py2.4.egg
+setuptools-0.6b4.zip
+setuptools-0.6c1-py2.3.egg
+setuptools-0.6c1-py2.4.egg
+setuptools-0.6c1.zip
+setuptools-0.6c2-py2.3.egg
+setuptools-0.6c2-py2.4.egg
+setuptools-0.6c2.zip
+setuptools-0.6c3-py2.3.egg
+setuptools-0.6c3-py2.4.egg
+setuptools-0.6c3-py2.5.egg
+setuptools-0.6c3.tar.gz
+setuptools-0.6c3.zip
+setuptools-0.6c4-1.src.rpm
+setuptools-0.6c4-py2.3.egg
+setuptools-0.6c4-py2.4.egg
+setuptools-0.6c4-py2.5.egg
+setuptools-0.6c4.tar.gz
+setuptools-0.6c4.win32-py2.3.exe
+setuptools-0.6c4.win32-py2.4.exe
+setuptools-0.6c4.win32-py2.5.exe
+setuptools-0.6c4.zip
+setuptools-0.6c5-1.src.rpm
+setuptools-0.6c5-py2.3.egg
+setuptools-0.6c5-py2.4.egg
+setuptools-0.6c5-py2.5.egg
+setuptools-0.6c5.tar.gz
+setuptools-0.6c5.win32-py2.3.exe
+setuptools-0.6c5.win32-py2.4.exe
+setuptools-0.6c5.win32-py2.5.exe
+setuptools-0.6c5.zip
+setuptools-0.6c6-1.src.rpm
+setuptools-0.6c6-py2.3.egg
+setuptools-0.6c6-py2.4.egg
+setuptools-0.6c6-py2.5.egg
+setuptools-0.6c6.tar.gz
+setuptools-0.6c6.win32-py2.3.exe
+setuptools-0.6c6.win32-py2.4.exe
+setuptools-0.6c6.win32-py2.5.exe
+setuptools-0.6c6.zip
+setuptools-0.6c7-1.src.rpm
+setuptools-0.6c7-py2.3.egg
+setuptools-0.6c7-py2.4.egg
+setuptools-0.6c7-py2.5.egg
+setuptools-0.6c7.tar.gz
+setuptools-0.6c7.win32-py2.3.exe
+setuptools-0.6c7.win32-py2.4.exe
+setuptools-0.6c7.win32-py2.5.exe
+setuptools-0.6c7.zip
+setuptools-0.6c8-1.src.rpm
+setuptools-0.6c8-py2.3.egg
+setuptools-0.6c8-py2.4.egg
+setuptools-0.6c8-py2.5.egg
+setuptools-0.6c8.tar.gz
+setuptools-0.6c8.win32-py2.3.exe
+setuptools-0.6c8.win32-py2.4.exe
+setuptools-0.6c8.win32-py2.5.exe
+setuptools-0.6c8.zip
+setuptools-0.6c9-1.src.rpm
+setuptools-0.6c9-py2.3.egg
+setuptools-0.6c9-py2.4.egg
+setuptools-0.6c9-py2.5.egg
+setuptools-0.6c9-py2.6.egg
+setuptools-0.6c9.tar.gz
+setuptools-0.6c9.win32-py2.3.exe
+setuptools-0.6c9.win32-py2.4.exe
+setuptools-0.6c9.win32-py2.5.exe
+setuptools-0.6c9.zip
+setuptools-0.6c10-1.src.rpm
+setuptools-0.6c10-py2.3.egg
+setuptools-0.6c10-py2.4.egg
+setuptools-0.6c10-py2.5.egg
+setuptools-0.6c10-py2.6.egg
+setuptools-0.6c10.tar.gz
+setuptools-0.6c10.win32-py2.3.exe
+setuptools-0.6c10.win32-py2.4.exe
+setuptools-0.6c10.win32-py2.5.exe
+setuptools-0.6c10.win32-py2.6.exe
+setuptools-0.6c10.zip
+setuptools-0.6c11-1.src.rpm
+setuptools-0.6c11-py2.3.egg
+setuptools-0.6c11-py2.4.egg
+setuptools-0.6c11-py2.5.egg
+setuptools-0.6c11-py2.6.egg
+setuptools-0.6c11-py2.7.egg
+setuptools-0.6c11.tar.gz
+setuptools-0.6c11.win32-py2.3.exe
+setuptools-0.6c11.win32-py2.4.exe
+setuptools-0.6c11.win32-py2.5.exe
+setuptools-0.6c11.win32-py2.6.exe
+setuptools-0.6c11.win32-py2.7.exe
+setuptools-0.6c11.zip
+setuptools-0.7.2.tar.gz
+setuptools-0.7.2.zip
+setuptools-0.7.3.tar.gz
+setuptools-0.7.3.zip
+setuptools-0.7.4.tar.gz
+setuptools-0.7.4.zip
+setuptools-0.7.5.tar.gz
+setuptools-0.7.5.zip
+setuptools-0.7.6.tar.gz
+setuptools-0.7.6.zip
+setuptools-0.7.7.tar.gz
+setuptools-0.7.7.zip
+setuptools-0.7.8.tar.gz
+setuptools-0.7.8.zip
+setuptools-0.8.tar.gz
+setuptools-0.8.zip
+setuptools-0.9.tar.gz
+setuptools-0.9.zip
+setuptools-0.9.1.tar.gz
+setuptools-0.9.1.zip
+setuptools-0.9.2.tar.gz
+setuptools-0.9.2.zip
+setuptools-0.9.3.tar.gz
+setuptools-0.9.3.zip
+setuptools-0.9.4.tar.gz
+setuptools-0.9.4.zip
+setuptools-0.9.5.tar.gz
+setuptools-0.9.5.zip
+setuptools-0.9.6.tar.gz
+setuptools-0.9.6.zip
+setuptools-0.9.7.tar.gz
+setuptools-0.9.7.zip
+setuptools-0.9.8-py2.py3-none-any.whl
+setuptools-0.9.8-py33-none-any.whl
+setuptools-0.9.8.tar.gz
+setuptools-0.9.8.zip
+setuptools-1.0.tar.gz
+setuptools-1.0.zip
+setuptools-1.1.tar.gz
+setuptools-1.1.zip
+setuptools-1.1.1.tar.gz
+setuptools-1.1.1.zip
+setuptools-1.1.2.tar.gz
+setuptools-1.1.2.zip
+setuptools-1.1.3.tar.gz
+setuptools-1.1.3.zip
+setuptools-1.1.4.tar.gz
+setuptools-1.1.4.zip
+setuptools-1.1.5.tar.gz
+setuptools-1.1.5.zip
+setuptools-1.1.6-py2.py3-none-any.whl
+setuptools-1.1.6.tar.gz
+setuptools-1.1.6.zip
+setuptools-1.1.7.tar.gz
+setuptools-1.1.7.zip
+setuptools-1.2-py2.py3-none-any.whl
+setuptools-1.2.tar.gz
+setuptools-1.2.zip
+setuptools-1.3-py2.py3-none-any.whl
+setuptools-1.3.tar.gz
+setuptools-1.3.zip
+setuptools-1.3.1-py2.py3-none-any.whl
+setuptools-1.3.1.tar.gz
+setuptools-1.3.1.zip
+setuptools-1.3.2-py2.py3-none-any.whl
+setuptools-1.3.2.tar.gz
+setuptools-1.3.2.zip
+setuptools-1.4-py2.py3-none-any.whl
+setuptools-1.4.tar.gz
+setuptools-1.4.zip
+setuptools-1.4.1-py2.py3-none-any.whl
+setuptools-1.4.1.tar.gz
+setuptools-1.4.1.zip
+setuptools-1.4.2-py2.py3-none-any.whl
+setuptools-1.4.2.tar.gz
+setuptools-1.4.2.zip
+setuptools-2.0-py2.py3-none-any.whl
+setuptools-2.0.tar.gz
+setuptools-2.0.zip
+setuptools-2.0.1-py2.py3-none-any.whl
+setuptools-2.0.1.tar.gz
+setuptools-2.0.1.zip
+setuptools-2.0.2-py2.py3-none-any.whl
+setuptools-2.0.2.tar.gz
+setuptools-2.0.2.zip
+setuptools-2.1-py2.py3-none-any.whl
+setuptools-2.1.tar.gz
+setuptools-2.1.zip
+setuptools-2.1.1.tar.gz
+setuptools-2.1.1.zip
+setuptools-2.1.2-py2.py3-none-any.whl
+setuptools-2.1.2.tar.gz
+setuptools-2.1.2.zip
+setuptools-2.2-py2.py3-none-any.whl
+setuptools-2.2.tar.gz
+setuptools-2.2.zip
+setuptools-3.0.tar.gz
+setuptools-3.0.zip
+setuptools-3.0.1.tar.gz
+setuptools-3.0.1.zip
+setuptools-3.0.2.tar.gz
+setuptools-3.0.2.zip
+setuptools-3.1-py2.py3-none-any.whl
+setuptools-3.1.tar.gz
+setuptools-3.1.zip
+setuptools-3.2-py2.py3-none-any.whl
+setuptools-3.2.tar.gz
+setuptools-3.2.zip
+setuptools-3.3-py2.py3-none-any.whl
+setuptools-3.3.tar.gz
+setuptools-3.3.zip
+setuptools-3.4-py2.py3-none-any.whl
+setuptools-3.4.tar.gz
+setuptools-3.4.zip
+setuptools-3.4.1-py2.py3-none-any.whl
+setuptools-3.4.1.tar.gz
+setuptools-3.4.1.zip
+setuptools-3.4.2-py2.py3-none-any.whl
+setuptools-3.4.2.tar.gz
+setuptools-3.4.2.zip
+setuptools-3.4.3-py2.py3-none-any.whl
+setuptools-3.4.3.tar.gz
+setuptools-3.4.3.zip
+setuptools-3.4.4-py2.py3-none-any.whl
+setuptools-3.4.4.tar.gz
+setuptools-3.4.4.zip
+setuptools-3.5-py2.py3-none-any.whl
+setuptools-3.5.tar.gz
+setuptools-3.5.zip
+setuptools-3.5.1-py2.py3-none-any.whl
+setuptools-3.5.1.tar.gz
+setuptools-3.5.1.zip
+setuptools-3.5.2-py2.py3-none-any.whl
+setuptools-3.5.2.tar.gz
+setuptools-3.5.2.zip
+setuptools-3.6-py2.py3-none-any.whl
+setuptools-3.6.tar.gz
+setuptools-3.6.zip
+setuptools-3.7-py2.py3-none-any.whl
+setuptools-3.7.tar.gz
+setuptools-3.7.zip
+setuptools-3.7.1-py2.py3-none-any.whl
+setuptools-3.7.1.tar.gz
+setuptools-3.7.1.zip
+setuptools-3.8-py2.py3-none-any.whl
+setuptools-3.8.tar.gz
+setuptools-3.8.zip
+setuptools-3.8.1-py2.py3-none-any.whl
+setuptools-3.8.1.tar.gz
+setuptools-3.8.1.zip
+setuptools-4.0.tar.gz
+setuptools-4.0.zip
+setuptools-4.0.1.tar.gz
+setuptools-4.0.1.zip
+setuptools-5.0-py2.py3-none-any.whl
+setuptools-5.0.tar.gz
+setuptools-5.0.zip
+setuptools-5.0.1-py2.py3-none-any.whl
+setuptools-5.0.1.tar.gz
+setuptools-5.0.1.zip
+setuptools-5.0.2-py2.py3-none-any.whl
+setuptools-5.0.2.tar.gz
+setuptools-5.0.2.zip
+setuptools-5.1-py2.py3-none-any.whl
+setuptools-5.1.tar.gz
+setuptools-5.1.zip
+setuptools-5.2-py2.py3-none-any.whl
+setuptools-5.2.tar.gz
+setuptools-5.2.zip
+setuptools-5.3-py2.py3-none-any.whl
+setuptools-5.3.tar.gz
+setuptools-5.3.zip
+setuptools-5.4-py2.py3-none-any.whl
+setuptools-5.4.tar.gz
+setuptools-5.4.zip
+setuptools-5.4.1-py2.py3-none-any.whl
+setuptools-5.4.1.tar.gz
+setuptools-5.4.1.zip
+setuptools-5.4.2-py2.py3-none-any.whl
+setuptools-5.4.2.tar.gz
+setuptools-5.4.2.zip
+setuptools-5.5-py2.py3-none-any.whl
+setuptools-5.5.tar.gz
+setuptools-5.5.zip
+setuptools-5.5.1-py2.py3-none-any.whl
+setuptools-5.5.1.tar.gz
+setuptools-5.5.1.zip
+setuptools-5.6-py2.py3-none-any.whl
+setuptools-5.6.tar.gz
+setuptools-5.6.zip
+setuptools-5.7-py2.py3-none-any.whl
+setuptools-5.7.tar.gz
+setuptools-5.7.zip
+setuptools-5.8-py2.py3-none-any.whl
+setuptools-5.8.tar.gz
+setuptools-5.8.zip
+setuptools-6.0.1-py2.py3-none-any.whl
+setuptools-6.0.1.tar.gz
+setuptools-6.0.1.zip
+setuptools-6.0.2-py2.py3-none-any.whl
+setuptools-6.0.2.tar.gz
+setuptools-6.0.2.zip
+setuptools-6.1-py2.py3-none-any.whl
+setuptools-6.1.tar.gz
+setuptools-6.1.zip
+setuptools-7.0-py2.py3-none-any.whl
+setuptools-7.0.tar.gz
+setuptools-7.0.zip
+setuptools-8.0-py2.py3-none-any.whl
+setuptools-8.0.tar.gz
+setuptools-8.0.zip
+setuptools-8.0.1-py2.py3-none-any.whl
+setuptools-8.0.1.tar.gz
+setuptools-8.0.1.zip
+setuptools-8.0.2-py2.py3-none-any.whl
+setuptools-8.0.2.tar.gz
+setuptools-8.0.2.zip
+setuptools-8.0.3-py2.py3-none-any.whl
+setuptools-8.0.3.tar.gz
+setuptools-8.0.3.zip
+setuptools-8.0.4-py2.py3-none-any.whl
+setuptools-8.0.4.tar.gz
+setuptools-8.0.4.zip
+setuptools-8.1-py2.py3-none-any.whl
+setuptools-8.1.tar.gz
+setuptools-8.1.zip
+setuptools-8.2-py2.py3-none-any.whl
+setuptools-8.2.tar.gz
+setuptools-8.2.zip
+setuptools-8.2.1-py2.py3-none-any.whl
+setuptools-8.2.1.tar.gz
+setuptools-8.2.1.zip
+setuptools-8.3-py2.py3-none-any.whl
+setuptools-8.3.tar.gz
+setuptools-8.3.zip
+setuptools-9.0-py2.py3-none-any.whl
+setuptools-9.0.tar.gz
+setuptools-9.0.zip
+setuptools-9.0.1-py2.py3-none-any.whl
+setuptools-9.0.1.tar.gz
+setuptools-9.0.1.zip
+setuptools-9.1-py2.py3-none-any.whl
+setuptools-9.1.tar.gz
+setuptools-9.1.zip
+setuptools-10.0-py2.py3-none-any.whl
+setuptools-10.0.tar.gz
+setuptools-10.0.zip
+setuptools-10.0.1-py2.py3-none-any.whl
+setuptools-10.0.1.tar.gz
+setuptools-10.0.1.zip
+setuptools-10.1-py2.py3-none-any.whl
+setuptools-10.1.tar.gz
+setuptools-10.1.zip
+setuptools-10.2-py2.py3-none-any.whl
+setuptools-10.2.tar.gz
+setuptools-10.2.zip
+setuptools-10.2.1-py2.py3-none-any.whl
+setuptools-10.2.1.tar.gz
+setuptools-10.2.1.zip
+setuptools-11.0-py2.py3-none-any.whl
+setuptools-11.0.tar.gz
+setuptools-11.0.zip
+setuptools-11.1-py2.py3-none-any.whl
+setuptools-11.1.tar.gz
+setuptools-11.1.zip
+setuptools-11.2-py2.py3-none-any.whl
+setuptools-11.2.tar.gz
+setuptools-11.2.zip
+setuptools-11.3-py2.py3-none-any.whl
+setuptools-11.3.tar.gz
+setuptools-11.3.zip
+setuptools-11.3.1-py2.py3-none-any.whl
+setuptools-11.3.1.tar.gz
+setuptools-11.3.1.zip
+setuptools-12.0-py2.py3-none-any.whl
+setuptools-12.0.tar.gz
+setuptools-12.0.zip
+setuptools-12.0.1-py2.py3-none-any.whl
+setuptools-12.0.1.tar.gz
+setuptools-12.0.1.zip
+setuptools-12.0.2-py2.py3-none-any.whl
+setuptools-12.0.2.tar.gz
+setuptools-12.0.2.zip
+setuptools-12.0.3-py2.py3-none-any.whl
+setuptools-12.0.3.tar.gz
+setuptools-12.0.3.zip
+setuptools-12.0.4-py2.py3-none-any.whl
+setuptools-12.0.4.tar.gz
+setuptools-12.0.4.zip
+setuptools-12.0.5-py2.py3-none-any.whl
+setuptools-12.0.5.tar.gz
+setuptools-12.0.5.zip
+setuptools-12.1-py2.py3-none-any.whl
+setuptools-12.1.tar.gz
+setuptools-12.1.zip
+setuptools-12.2-py2.py3-none-any.whl
+setuptools-12.2.tar.gz
+setuptools-12.2.zip
+setuptools-12.3-py2.py3-none-any.whl
+setuptools-12.3.tar.gz
+setuptools-12.3.zip
+setuptools-12.4-py2.py3-none-any.whl
+setuptools-12.4.tar.gz
+setuptools-12.4.zip
+setuptools-13.0.1-py2.py3-none-any.whl
+setuptools-13.0.1.tar.gz
+setuptools-13.0.1.zip
+setuptools-13.0.2-py2.py3-none-any.whl
+setuptools-13.0.2.tar.gz
+setuptools-13.0.2.zip
+setuptools-14.0-py2.py3-none-any.whl
+setuptools-14.0.tar.gz
+setuptools-14.0.zip
+setuptools-14.1-py2.py3-none-any.whl
+setuptools-14.1.tar.gz
+setuptools-14.1.zip
+setuptools-14.1.1-py2.py3-none-any.whl
+setuptools-14.1.1.tar.gz
+setuptools-14.1.1.zip
+setuptools-14.2-py2.py3-none-any.whl
+setuptools-14.2.tar.gz
+setuptools-14.2.zip
+setuptools-14.3-py2.py3-none-any.whl
+setuptools-14.3.tar.gz
+setuptools-14.3.zip
+setuptools-14.3.1-py2.py3-none-any.whl
+setuptools-14.3.1.tar.gz
+setuptools-14.3.1.zip
+setuptools-15.0-py2.py3-none-any.whl
+setuptools-15.0.tar.gz
+setuptools-15.0.zip
+setuptools-15.1-py2.py3-none-any.whl
+setuptools-15.1.tar.gz
+setuptools-15.1.zip
+setuptools-15.2-py2.py3-none-any.whl
+setuptools-15.2.tar.gz
+setuptools-15.2.zip
+setuptools-16.0-py2.py3-none-any.whl
+setuptools-16.0.tar.gz
+setuptools-16.0.zip
+setuptools-17.0-py2.py3-none-any.whl
+setuptools-17.0.tar.gz
+setuptools-17.0.zip
+setuptools-17.1-py2.py3-none-any.whl
+setuptools-17.1.tar.gz
+setuptools-17.1.zip
+setuptools-17.1.1-py2.py3-none-any.whl
+setuptools-17.1.1.tar.gz
+setuptools-17.1.1.zip
+setuptools-18.0-py2.py3-none-any.whl
+setuptools-18.0.tar.gz
+setuptools-18.0.zip
+setuptools-18.0.1-py2.py3-none-any.whl
+setuptools-18.0.1.tar.gz
+setuptools-18.0.1.zip
+setuptools-18.1-py2.py3-none-any.whl
+setuptools-18.1.tar.gz
+setuptools-18.1.zip
+setuptools-18.2-py2.py3-none-any.whl
+setuptools-18.2.tar.gz
+setuptools-18.2.zip
+setuptools-18.3-py2.py3-none-any.whl
+setuptools-18.3.tar.gz
+setuptools-18.3.zip
+setuptools-18.3.1-py2.py3-none-any.whl
+setuptools-18.3.1-py3.4.egg
+setuptools-18.3.1.tar.gz
+setuptools-18.3.1.zip
+setuptools-18.3.2-py2.py3-none-any.whl
+setuptools-18.3.2.tar.gz
+setuptools-18.3.2.zip
+setuptools-18.4-py2.py3-none-any.whl
+setuptools-18.4.tar.gz
+setuptools-18.4.zip
+setuptools-18.5-py2.py3-none-any.whl
+setuptools-18.5.tar.gz
+setuptools-18.5.zip
+setuptools-18.6-py2.py3-none-any.whl
+setuptools-18.6.tar.gz
+setuptools-18.6.zip
+setuptools-18.6.1-py2.py3-none-any.whl
+setuptools-18.6.1.tar.gz
+setuptools-18.6.1.zip
+setuptools-18.7-py2.py3-none-any.whl
+setuptools-18.7.tar.gz
+setuptools-18.7.zip
+setuptools-18.7.1-py2.py3-none-any.whl
+setuptools-18.7.1.tar.gz
+setuptools-18.7.1.zip
+setuptools-18.8-py2.py3-none-any.whl
+setuptools-18.8.tar.gz
+setuptools-18.8.zip
+setuptools-18.8.1-py2.py3-none-any.whl
+setuptools-18.8.1.tar.gz
+setuptools-18.8.1.zip
+setuptools-19.0-py2.py3-none-any.whl
+setuptools-19.0.tar.gz
+setuptools-19.0.zip
+setuptools-19.1-py2.py3-none-any.whl
+setuptools-19.1.tar.gz
+setuptools-19.1.zip
+setuptools-19.1.1-py2.py3-none-any.whl
+setuptools-19.1.1.tar.gz
+setuptools-19.1.1.zip
+setuptools-19.2-py2.py3-none-any.whl
+setuptools-19.2.tar.gz
+setuptools-19.2.zip
+setuptools-19.3-py2.py3-none-any.whl
+setuptools-19.3.tar.gz
+setuptools-19.3.zip
+setuptools-19.4-py2.py3-none-any.whl
+setuptools-19.4.tar.gz
+setuptools-19.4.zip
+setuptools-19.4.1-py2.py3-none-any.whl
+setuptools-19.4.1.tar.gz
+setuptools-19.4.1.zip
+setuptools-19.5-py2.py3-none-any.whl
+setuptools-19.5.tar.gz
+setuptools-19.5.zip
+setuptools-19.6-py2.py3-none-any.whl
+setuptools-19.6.tar.gz
+setuptools-19.6.zip
+setuptools-19.6.1-py2.py3-none-any.whl
+setuptools-19.6.1.tar.gz
+setuptools-19.6.1.zip
+setuptools-19.6.2-py2.py3-none-any.whl
+setuptools-19.6.2.tar.gz
+setuptools-19.6.2.zip
+setuptools-19.7-py2.py3-none-any.whl
+setuptools-19.7.tar.gz
+setuptools-19.7.zip
+setuptools-20.0-py2.py3-none-any.whl
+setuptools-20.0.tar.gz
+setuptools-20.0.zip
+setuptools-20.1-py2.py3-none-any.whl
+setuptools-20.1.tar.gz
+setuptools-20.1.zip
+setuptools-20.1.1-py2.py3-none-any.whl
+setuptools-20.1.1.tar.gz
+setuptools-20.1.1.zip
+setuptools-20.2.2-py2.py3-none-any.whl
+setuptools-20.2.2.tar.gz
+setuptools-20.2.2.zip
+setuptools-20.3-py2.py3-none-any.whl
+setuptools-20.3.tar.gz
+setuptools-20.3.zip
+setuptools-20.3.1-py2.py3-none-any.whl
+setuptools-20.3.1.tar.gz
+setuptools-20.3.1.zip
+setuptools-20.4-py2.py3-none-any.whl
+setuptools-20.4.tar.gz
+setuptools-20.4.zip
+setuptools-20.6.6-py2.py3-none-any.whl
+setuptools-20.6.6.tar.gz
+setuptools-20.6.6.zip
+setuptools-20.6.7-py2.py3-none-any.whl
+setuptools-20.6.7.tar.gz
+setuptools-20.6.7.zip
+setuptools-20.6.8-py2.py3-none-any.whl
+setuptools-20.6.8.tar.gz
+setuptools-20.6.8.zip
+setuptools-20.7.0-py2.py3-none-any.whl
+setuptools-20.7.0.tar.gz
+setuptools-20.7.0.zip
+setuptools-20.8.0-py2.py3-none-any.whl
+setuptools-20.8.0.tar.gz
+setuptools-20.8.0.zip
+setuptools-20.8.1-py2.py3-none-any.whl
+setuptools-20.8.1.tar.gz
+setuptools-20.8.1.zip
+setuptools-20.9.0-py2.py3-none-any.whl
+setuptools-20.9.0.tar.gz
+setuptools-20.9.0.zip
+setuptools-20.10.1-py2.py3-none-any.whl
+setuptools-20.10.1.tar.gz
+setuptools-20.10.1.zip
+setuptools-21.0.0-py2.py3-none-any.whl
+setuptools-21.0.0.tar.gz
+setuptools-21.0.0.zip
+setuptools-21.1.0-py2.py3-none-any.whl
+setuptools-21.1.0.tar.gz
+setuptools-21.1.0.zip
+setuptools-21.2.0-py2.py3-none-any.whl
+setuptools-21.2.0.tar.gz
+setuptools-21.2.0.zip
+setuptools-21.2.1-py2.py3-none-any.whl
+setuptools-21.2.1.tar.gz
+setuptools-21.2.1.zip
+setuptools-21.2.2-py2.py3-none-any.whl
+setuptools-21.2.2.tar.gz
+setuptools-21.2.2.zip
+setuptools-22.0.0-py2.py3-none-any.whl
+setuptools-22.0.0.tar.gz
+setuptools-22.0.0.zip
+setuptools-22.0.1-py2.py3-none-any.whl
+setuptools-22.0.1.tar.gz
+setuptools-22.0.1.zip
+setuptools-22.0.2-py2.py3-none-any.whl
+setuptools-22.0.2.tar.gz
+setuptools-22.0.2.zip
+setuptools-22.0.4-py2.py3-none-any.whl
+setuptools-22.0.4.tar.gz
+setuptools-22.0.4.zip
+setuptools-22.0.5-py2.py3-none-any.whl
+setuptools-22.0.5.tar.gz
+setuptools-22.0.5.zip
+setuptools-23.0.0-py2.py3-none-any.whl
+setuptools-23.0.0.tar.gz
+setuptools-23.0.0.zip
+setuptools-23.1.0-py2.py3-none-any.whl
+setuptools-23.1.0.tar.gz
+setuptools-23.1.0.zip
+setuptools-23.2.0-py2.py3-none-any.whl
+setuptools-23.2.0.tar.gz
+setuptools-23.2.0.zip
+setuptools-23.2.1-py2.py3-none-any.whl
+setuptools-23.2.1.tar.gz
+setuptools-23.2.1.zip
+setuptools-24.0.0-py2.py3-none-any.whl
+setuptools-24.0.0.tar.gz
+setuptools-24.0.0.zip
+setuptools-24.0.1-py2.py3-none-any.whl
+setuptools-24.0.1.tar.gz
+setuptools-24.0.1.zip
+setuptools-24.0.2-py2.py3-none-any.whl
+setuptools-24.0.2.tar.gz
+setuptools-24.0.2.zip
+setuptools-24.0.3-py2.py3-none-any.whl
+setuptools-24.0.3.tar.gz
+setuptools-24.0.3.zip
+setuptools-24.1.0-py2.py3-none-any.whl
+setuptools-24.1.0.tar.gz
+setuptools-24.1.0.zip
+setuptools-24.1.1-py2.py3-none-any.whl
+setuptools-24.1.1.tar.gz
+setuptools-24.1.1.zip
+setuptools-24.2.0-py2.py3-none-any.whl
+setuptools-24.2.0.tar.gz
+setuptools-24.2.0.zip
+setuptools-24.2.1-py2.py3-none-any.whl
+setuptools-24.2.1.tar.gz
+setuptools-24.2.1.zip
+setuptools-24.3.0-py2.py3-none-any.whl
+setuptools-24.3.0.tar.gz
+setuptools-24.3.0.zip
+setuptools-24.3.1-py2.py3-none-any.whl
+setuptools-24.3.1.tar.gz
+setuptools-24.3.1.zip
+setuptools-25.0.0-py2.py3-none-any.whl
+setuptools-25.0.0.tar.gz
+setuptools-25.0.0.zip
+setuptools-25.0.1-py2.py3-none-any.whl
+setuptools-25.0.1.tar.gz
+setuptools-25.0.1.zip
+setuptools-25.0.2-py2.py3-none-any.whl
+setuptools-25.0.2.tar.gz
+setuptools-25.0.2.zip
+setuptools-25.1.0-py2.py3-none-any.whl
+setuptools-25.1.0.tar.gz
+setuptools-25.1.0.zip
+setuptools-25.1.1-py2.py3-none-any.whl
+setuptools-25.1.1.tar.gz
+setuptools-25.1.1.zip
+setuptools-25.1.2-py2.py3-none-any.whl
+setuptools-25.1.2.tar.gz
+setuptools-25.1.2.zip
+setuptools-25.1.3-py2.py3-none-any.whl
+setuptools-25.1.3.tar.gz
+setuptools-25.1.3.zip
+setuptools-25.1.4-py2.py3-none-any.whl
+setuptools-25.1.4.tar.gz
+setuptools-25.1.4.zip
+setuptools-25.1.5-py2.py3-none-any.whl
+setuptools-25.1.5.tar.gz
+setuptools-25.1.5.zip
+setuptools-25.1.6-py2.py3-none-any.whl
+setuptools-25.1.6.tar.gz
+setuptools-25.1.6.zip
+setuptools-25.2.0-py2.py3-none-any.whl
+setuptools-25.2.0.tar.gz
+setuptools-25.2.0.zip
+setuptools-25.3.0-py2.py3-none-any.whl
+setuptools-25.3.0.tar.gz
+setuptools-25.3.0.zip
+setuptools-25.4.0-py2.py3-none-any.whl
+setuptools-25.4.0.tar.gz
+setuptools-25.4.0.zip
+setuptools-26.0.0-py2.py3-none-any.whl
+setuptools-26.0.0.tar.gz
+setuptools-26.0.0.zip
+setuptools-26.1.0-py2.py3-none-any.whl
+setuptools-26.1.0.tar.gz
+setuptools-26.1.0.zip
+setuptools-26.1.1-py2.py3-none-any.whl
+setuptools-26.1.1.tar.gz
+setuptools-26.1.1.zip
+setuptools-27.0.0-py2.py3-none-any.whl
+setuptools-27.0.0.tar.gz
+setuptools-27.0.0.zip
+setuptools-27.1.0-py2.py3-none-any.whl
+setuptools-27.1.0.tar.gz
+setuptools-27.1.0.zip
+setuptools-27.1.2-py2.py3-none-any.whl
+setuptools-27.1.2.tar.gz
+setuptools-27.1.2.zip
+setuptools-27.2.0-py2.py3-none-any.whl
+setuptools-27.2.0.tar.gz
+setuptools-27.2.0.zip
+setuptools-27.3.0-py2.py3-none-any.whl
+setuptools-27.3.0.tar.gz
+setuptools-27.3.0.zip
+setuptools-27.3.1-py2.py3-none-any.whl
+setuptools-27.3.1.tar.gz
+setuptools-27.3.1.zip
+setuptools-28.0.0-py2.py3-none-any.whl
+setuptools-28.0.0.tar.gz
+setuptools-28.0.0.zip
+setuptools-28.1.0-py2.py3-none-any.whl
+setuptools-28.1.0.tar.gz
+setuptools-28.1.0.zip
+setuptools-28.2.0-py2.py3-none-any.whl
+setuptools-28.2.0.tar.gz
+setuptools-28.2.0.zip
+setuptools-28.3.0-py2.py3-none-any.whl
+setuptools-28.3.0.tar.gz
+setuptools-28.3.0.zip
+setuptools-28.4.0-py2.py3-none-any.whl
+setuptools-28.4.0.tar.gz
+setuptools-28.4.0.zip
+setuptools-28.5.0-py2.py3-none-any.whl
+setuptools-28.5.0.tar.gz
+setuptools-28.5.0.zip
+setuptools-28.6.0-py2.py3-none-any.whl
+setuptools-28.6.0.tar.gz
+setuptools-28.6.0.zip
+setuptools-28.6.1-py2.py3-none-any.whl
+setuptools-28.6.1.tar.gz
+setuptools-28.6.1.zip
+setuptools-28.7.0-py2.py3-none-any.whl
+setuptools-28.7.0.tar.gz
+setuptools-28.7.0.zip
+setuptools-28.7.1-py2.py3-none-any.whl
+setuptools-28.7.1.tar.gz
+setuptools-28.7.1.zip
+setuptools-28.8.0-py2.py3-none-any.whl
+setuptools-28.8.0.tar.gz
+setuptools-28.8.0.zip
+setuptools-28.8.1-py2.py3-none-any.whl
+setuptools-28.8.1.tar.gz
+setuptools-28.8.1.zip
+setuptools-29.0.0-py2.py3-none-any.whl
+setuptools-29.0.0.tar.gz
+setuptools-29.0.0.zip
+setuptools-29.0.1-py2.py3-none-any.whl
+setuptools-29.0.1.tar.gz
+setuptools-29.0.1.zip
+setuptools-30.0.0-py2.py3-none-any.whl
+setuptools-30.0.0.tar.gz
+setuptools-30.0.0.zip
+setuptools-30.1.0-py2.py3-none-any.whl
+setuptools-30.1.0.tar.gz
+setuptools-30.1.0.zip
+setuptools-30.2.0-py2.py3-none-any.whl
+setuptools-30.2.0.tar.gz
+setuptools-30.2.0.zip
+setuptools-30.2.1-py2.py3-none-any.whl
+setuptools-30.2.1.tar.gz
+setuptools-30.2.1.zip
+setuptools-30.3.0-py2.py3-none-any.whl
+setuptools-30.3.0.tar.gz
+setuptools-30.3.0.zip
+setuptools-30.4.0-py2.py3-none-any.whl
+setuptools-30.4.0.tar.gz
+setuptools-30.4.0.zip
+setuptools-31.0.0-py2.py3-none-any.whl
+setuptools-31.0.0.tar.gz
+setuptools-31.0.0.zip
+setuptools-31.0.1-py2.py3-none-any.whl
+setuptools-31.0.1.tar.gz
+setuptools-31.0.1.zip
+setuptools-32.0.0-py2.py3-none-any.whl
+setuptools-32.0.0.tar.gz
+setuptools-32.0.0.zip
+setuptools-32.1.0-py2.py3-none-any.whl
+setuptools-32.1.0.tar.gz
+setuptools-32.1.0.zip
+setuptools-32.1.1-py2.py3-none-any.whl
+setuptools-32.1.1.tar.gz
+setuptools-32.1.2-py2.py3-none-any.whl
+setuptools-32.1.2.zip
+setuptools-32.1.3-py2.py3-none-any.whl
+setuptools-32.1.3.zip
+setuptools-32.2.0-py2.py3-none-any.whl
+setuptools-32.2.0.zip
+setuptools-32.3.0-py2.py3-none-any.whl
+setuptools-32.3.0.zip
+setuptools-32.3.1-py2.py3-none-any.whl
+setuptools-32.3.1.zip
+setuptools-33.1.0-py2.py3-none-any.whl
+setuptools-33.1.0.zip
+setuptools-33.1.1-py2.py3-none-any.whl
+setuptools-33.1.1.zip
+setuptools-34.0.0-py2.py3-none-any.whl
+setuptools-34.0.0.zip
+setuptools-34.0.1-py2.py3-none-any.whl
+setuptools-34.0.1.zip
+setuptools-34.0.2-py2.py3-none-any.whl
+setuptools-34.0.2.zip
+setuptools-34.0.3-py2.py3-none-any.whl
+setuptools-34.0.3.zip
+setuptools-34.1.0-py2.py3-none-any.whl
+setuptools-34.1.0.zip
+setuptools-34.1.1-py2.py3-none-any.whl
+setuptools-34.1.1.zip
+setuptools-34.2.0-py2.py3-none-any.whl
+setuptools-34.2.0.zip
+setuptools-34.3.0-py2.py3-none-any.whl
+setuptools-34.3.0.zip
+setuptools-34.3.1-py2.py3-none-any.whl
+setuptools-34.3.1.zip
+setuptools-34.3.2-py2.py3-none-any.whl
+setuptools-34.3.2.zip
+setuptools-34.3.3-py2.py3-none-any.whl
+setuptools-34.3.3.zip
+setuptools-34.4.0-py2.py3-none-any.whl
+setuptools-34.4.0.zip
+setuptools-34.4.1-py2.py3-none-any.whl
+setuptools-34.4.1.zip
+setuptools-35.0.0-py2.py3-none-any.whl
+setuptools-35.0.0.zip
+setuptools-35.0.1-py2.py3-none-any.whl
+setuptools-35.0.1.zip
+setuptools-35.0.2-py2.py3-none-any.whl
+setuptools-35.0.2.zip
+setuptools-36.0.1-py2.py3-none-any.whl
+setuptools-36.0.1.zip
+setuptools-36.1.0-py2.py3-none-any.whl
+setuptools-36.1.0.zip
+setuptools-36.1.1-py2.py3-none-any.whl
+setuptools-36.1.1.zip
+setuptools-36.2.0-py2.py3-none-any.whl
+setuptools-36.2.0.zip
+setuptools-36.2.1-py2.py3-none-any.whl
+setuptools-36.2.1.zip
+setuptools-36.2.2-py2.py3-none-any.whl
+setuptools-36.2.2.zip
+setuptools-36.2.3-py2.py3-none-any.whl
+setuptools-36.2.3.zip
+setuptools-36.2.4-py2.py3-none-any.whl
+setuptools-36.2.4.zip
+setuptools-36.2.5-py2.py3-none-any.whl
+setuptools-36.2.5.zip
+setuptools-36.2.6-py2.py3-none-any.whl
+setuptools-36.2.6.zip
+setuptools-36.2.7-py2.py3-none-any.whl
+setuptools-36.2.7.zip
+setuptools-36.3.0-py2.py3-none-any.whl
+setuptools-36.3.0.zip
+setuptools-36.4.0-py2.py3-none-any.whl
+setuptools-36.4.0.zip
+setuptools-36.5.0-py2.py3-none-any.whl
+setuptools-36.5.0.zip
+setuptools-36.6.0-py2.py3-none-any.whl
+setuptools-36.6.0.zip
+setuptools-36.6.1-py2.py3-none-any.whl
+setuptools-36.6.1.zip
+setuptools-36.7.0-py2.py3-none-any.whl
+setuptools-36.7.0.zip
+setuptools-36.7.1-py2.py3-none-any.whl
+setuptools-36.7.1.zip
+setuptools-36.7.2-py2.py3-none-any.whl
+setuptools-36.7.2.zip
+setuptools-36.8.0-py2.py3-none-any.whl
+setuptools-36.8.0.zip
+setuptools-37.0.0-py2.py3-none-any.whl
+setuptools-37.0.0.zip
+setuptools-38.0.0-py2.py3-none-any.whl
+setuptools-38.0.0.zip
+setuptools-38.1.0-py2.py3-none-any.whl
+setuptools-38.1.0.zip
+setuptools-38.2.0-py2.py3-none-any.whl
+setuptools-38.2.0.zip
+setuptools-38.2.1-py2.py3-none-any.whl
+setuptools-38.2.1.zip
+setuptools-38.2.3-py2.py3-none-any.whl
+setuptools-38.2.3.zip
+setuptools-38.2.4-py2.py3-none-any.whl
+setuptools-38.2.4.zip
+setuptools-38.2.5-py2.py3-none-any.whl
+setuptools-38.2.5.zip
+setuptools-38.3.0-py2.py3-none-any.whl
+setuptools-38.3.0.zip
+setuptools-38.4.0-py2.py3-none-any.whl
+setuptools-38.4.0.zip
+setuptools-38.4.1-py2.py3-none-any.whl
+setuptools-38.4.1.zip
+setuptools-38.5.0-py2.py3-none-any.whl
+setuptools-38.5.0.zip
+setuptools-38.5.1-py2.py3-none-any.whl
+setuptools-38.5.1.zip
+setuptools-38.5.2-py2.py3-none-any.whl
+setuptools-38.5.2.zip
+setuptools-38.6.0-py2.py3-none-any.whl
+setuptools-38.6.0.zip
+setuptools-38.6.1-py2.py3-none-any.whl
+setuptools-38.6.1.zip
+setuptools-38.7.0-py2.py3-none-any.whl
+setuptools-38.7.0.zip
+setuptools-39.0.0-py2.py3-none-any.whl
+setuptools-39.0.0.zip
+setuptools-39.0.1-py2.py3-none-any.whl
+setuptools-39.0.1.zip
+setuptools-39.1.0-py2.py3-none-any.whl
+setuptools-39.1.0.zip
+setuptools-39.2.0-py2.py3-none-any.whl
+setuptools-39.2.0.zip
+setuptools-40.0.0-py2.py3-none-any.whl
+setuptools-40.0.0.zip
+setuptools-40.1.0-py2.py3-none-any.whl
+setuptools-40.1.0.zip
+setuptools-40.1.1-py2.py3-none-any.whl
+setuptools-40.1.1.zip
+setuptools-40.2.0-py2.py3-none-any.whl
+setuptools-40.2.0.zip
+setuptools-40.3.0-py2.py3-none-any.whl
+setuptools-40.3.0.zip
+setuptools-40.4.0-py2.py3-none-any.whl
+setuptools-40.4.0.zip
+setuptools-40.4.1-py2.py3-none-any.whl
+setuptools-40.4.1.zip
+setuptools-40.4.2-py2.py3-none-any.whl
+setuptools-40.4.2.zip
+setuptools-40.4.3-py2.py3-none-any.whl
+setuptools-40.4.3.zip
+setuptools-40.5.0-py2.py3-none-any.whl
+setuptools-40.5.0.zip
+setuptools-40.6.0-py2.py3-none-any.whl
+setuptools-40.6.0.zip
+setuptools-40.6.1-py2.py3-none-any.whl
+setuptools-40.6.1.zip
+setuptools-40.6.2-py2.py3-none-any.whl
+setuptools-40.6.2.zip
+setuptools-40.6.3-py2.py3-none-any.whl
+setuptools-40.6.3.zip
+setuptools-40.7.0-py2.py3-none-any.whl
+setuptools-40.7.0.zip
+setuptools-40.7.1-py2.py3-none-any.whl
+setuptools-40.7.1.zip
+setuptools-40.7.2-py2.py3-none-any.whl
+setuptools-40.7.2.zip
+setuptools-40.7.3-py2.py3-none-any.whl
+setuptools-40.7.3.zip
+setuptools-40.8.0-py2.py3-none-any.whl
+setuptools-40.8.0.zip
+setuptools-40.9.0-py2.py3-none-any.whl
+setuptools-40.9.0.zip
+setuptools-41.0.0-py2.py3-none-any.whl
+setuptools-41.0.0.zip
+setuptools-41.0.1-py2.py3-none-any.whl
+setuptools-41.0.1.zip
+setuptools-41.1.0-py2.py3-none-any.whl
+setuptools-41.1.0.zip
+setuptools-41.2.0-py2.py3-none-any.whl
+setuptools-41.2.0.zip
+setuptools-41.3.0-py2.py3-none-any.whl
+setuptools-41.3.0.zip
+setuptools-41.4.0-py2.py3-none-any.whl
+setuptools-41.4.0.zip
+setuptools-41.5.0-py2.py3-none-any.whl
+setuptools-41.5.0.zip
+setuptools-41.5.1-py2.py3-none-any.whl
+setuptools-41.5.1.zip
+setuptools-41.6.0-py2.py3-none-any.whl
+setuptools-41.6.0.zip
+setuptools-42.0.0-py2.py3-none-any.whl
+setuptools-42.0.0.zip
+setuptools-42.0.1-py2.py3-none-any.whl
+setuptools-42.0.1.zip
+setuptools-42.0.2-py2.py3-none-any.whl
+setuptools-42.0.2.zip
+setuptools-43.0.0-py2.py3-none-any.whl
+setuptools-43.0.0.zip
+setuptools-44.0.0-py2.py3-none-any.whl
+setuptools-44.0.0.zip
+setuptools-44.1.0-py2.py3-none-any.whl
+setuptools-44.1.0.zip
+setuptools-44.1.1-py2.py3-none-any.whl
+setuptools-44.1.1.zip
+setuptools-45.0.0-py2.py3-none-any.whl
+setuptools-45.0.0.zip
+setuptools-45.1.0-py3-none-any.whl
+setuptools-45.1.0.zip
+setuptools-45.2.0-py3-none-any.whl
+setuptools-45.2.0.zip
+setuptools-45.3.0-py3-none-any.whl
+setuptools-45.3.0.zip
+setuptools-46.0.0-py3-none-any.whl
+setuptools-46.0.0.zip
+setuptools-46.1.0-py3-none-any.whl
+setuptools-46.1.0.zip
+setuptools-46.1.1-py3-none-any.whl
+setuptools-46.1.1.zip
+setuptools-46.1.2-py3-none-any.whl
+setuptools-46.1.2.zip
+setuptools-46.1.3-py3-none-any.whl
+setuptools-46.1.3.zip
+setuptools-46.2.0-py3-none-any.whl
+setuptools-46.2.0.zip
+setuptools-46.3.0-py3-none-any.whl
+setuptools-46.3.0.zip
+setuptools-46.3.1-py3-none-any.whl
+setuptools-46.3.1.zip
+setuptools-46.4.0-py3-none-any.whl
+setuptools-46.4.0.zip
+setuptools-47.0.0-py3-none-any.whl
+setuptools-47.0.0.zip
+setuptools-47.1.0-py3-none-any.whl
+setuptools-47.1.0.zip
+setuptools-47.1.1-py3-none-any.whl
+setuptools-47.1.1.zip
+setuptools-47.2.0-py3-none-any.whl
+setuptools-47.2.0.zip
+setuptools-47.3.0-py3-none-any.whl
+setuptools-47.3.0.zip
+setuptools-47.3.1-py3-none-any.whl
+setuptools-47.3.1.zip
+setuptools-47.3.2-py3-none-any.whl
+setuptools-47.3.2.zip
+setuptools-48.0.0-py3-none-any.whl
+setuptools-48.0.0.zip
+setuptools-49.0.0-py3-none-any.whl
+setuptools-49.0.0.zip
+setuptools-49.0.1-py3-none-any.whl
+setuptools-49.0.1.zip
+setuptools-49.1.0-py3-none-any.whl
+setuptools-49.1.0.zip
+setuptools-49.1.1-py3-none-any.whl
+setuptools-49.1.1.zip
+setuptools-49.1.2-py3-none-any.whl
+setuptools-49.1.2.zip
+setuptools-49.1.3-py3-none-any.whl
+setuptools-49.1.3.zip
+setuptools-49.2.0-py3-none-any.whl
+setuptools-49.2.0.zip
+setuptools-49.2.1-py3-none-any.whl
+setuptools-49.2.1.zip
+setuptools-49.3.0-py3-none-any.whl
+setuptools-49.3.0.zip
+setuptools-49.3.1-py3-none-any.whl
+setuptools-49.3.1.zip
+setuptools-49.3.2-py3-none-any.whl
+setuptools-49.3.2.zip
+setuptools-49.4.0-py3-none-any.whl
+setuptools-49.4.0.zip
+setuptools-49.5.0-py3-none-any.whl
+setuptools-49.5.0.zip
+setuptools-49.6.0-py3-none-any.whl
+setuptools-49.6.0.zip
+setuptools-50.0.0-py3-none-any.whl
+setuptools-50.0.0.zip
+setuptools-50.0.1-py3-none-any.whl
+setuptools-50.0.1.zip
+setuptools-50.0.2-py3-none-any.whl
+setuptools-50.0.2.zip
+setuptools-50.0.3-py3-none-any.whl
+setuptools-50.0.3.zip
+setuptools-50.1.0-py3-none-any.whl
+setuptools-50.1.0.zip
+setuptools-50.2.0-py3-none-any.whl
+setuptools-50.2.0.zip
+setuptools-50.3.0-py3-none-any.whl
+setuptools-50.3.0.zip
+setuptools-50.3.1-py3-none-any.whl
+setuptools-50.3.1.zip
+setuptools-50.3.2-py3-none-any.whl
+setuptools-50.3.2.zip
+setuptools-51.0.0-py3-none-any.whl
+setuptools-51.0.0.zip
+setuptools-51.1.0-py3-none-any.whl
+setuptools-51.1.0.tar.gz
+setuptools-51.1.0.post20201221-py3-none-any.whl
+setuptools-51.1.0.post20201221.tar.gz
+setuptools-51.1.1-py3-none-any.whl
+setuptools-51.1.1.tar.gz
+setuptools-51.1.2-py3-none-any.whl
+setuptools-51.1.2.tar.gz
+setuptools-51.2.0-py3-none-any.whl
+setuptools-51.2.0.tar.gz
+setuptools-51.3.0-py3-none-any.whl
+setuptools-51.3.0.tar.gz
+setuptools-51.3.1-py3-none-any.whl
+setuptools-51.3.1.tar.gz
+setuptools-51.3.2-py3-none-any.whl
+setuptools-51.3.2.tar.gz
+setuptools-51.3.3-py3-none-any.whl
+setuptools-51.3.3.tar.gz
+setuptools-52.0.0-py3-none-any.whl
+setuptools-52.0.0.tar.gz
+setuptools-53.0.0-py3-none-any.whl
+setuptools-53.0.0.tar.gz
+setuptools-53.1.0-py3-none-any.whl
+setuptools-53.1.0.tar.gz
+setuptools-54.0.0-py3-none-any.whl
+setuptools-54.0.0.tar.gz
+setuptools-54.1.0-py3-none-any.whl
+setuptools-54.1.0.tar.gz
+setuptools-54.1.1-py3-none-any.whl
+setuptools-54.1.1.tar.gz
+setuptools-54.1.2-py3-none-any.whl
+setuptools-54.1.2.tar.gz
+setuptools-54.1.3-py3-none-any.whl
+setuptools-54.1.3.tar.gz
+setuptools-54.2.0-py3-none-any.whl
+setuptools-54.2.0.tar.gz
+setuptools-56.0.0-py3-none-any.whl
+setuptools-56.0.0.tar.gz
+setuptools-56.1.0-py3-none-any.whl
+setuptools-56.1.0.tar.gz
+setuptools-56.2.0-py3-none-any.whl
+setuptools-56.2.0.tar.gz
+setuptools-57.0.0-py3-none-any.whl
+setuptools-57.0.0.tar.gz
+setuptools-57.1.0-py3-none-any.whl
+setuptools-57.1.0.tar.gz
+setuptools-57.2.0-py3-none-any.whl
+setuptools-57.2.0.tar.gz
+setuptools-57.3.0-py3-none-any.whl
+setuptools-57.3.0.tar.gz
+setuptools-57.4.0-py3-none-any.whl
+setuptools-57.4.0.tar.gz
+setuptools-57.5.0-py3-none-any.whl
+setuptools-57.5.0.tar.gz
+setuptools-58.0.0-py3-none-any.whl
+setuptools-58.0.0.tar.gz
+setuptools-58.0.1-py3-none-any.whl
+setuptools-58.0.1.tar.gz
+setuptools-58.0.2-py3-none-any.whl
+setuptools-58.0.2.tar.gz
+setuptools-58.0.3-py3-none-any.whl
+setuptools-58.0.3.tar.gz
+setuptools-58.0.4-py3-none-any.whl
+setuptools-58.0.4.tar.gz
+setuptools-58.1.0-py3-none-any.whl
+setuptools-58.1.0.tar.gz
+setuptools-58.2.0-py3-none-any.whl
+setuptools-58.2.0.tar.gz
+setuptools-58.3.0-py3-none-any.whl
+setuptools-58.3.0.tar.gz
+setuptools-58.4.0-py3-none-any.whl
+setuptools-58.4.0.tar.gz
+setuptools-58.5.0-py3-none-any.whl
+setuptools-58.5.0.tar.gz
+setuptools-58.5.1-py3-none-any.whl
+setuptools-58.5.1.tar.gz
+setuptools-58.5.2-py3-none-any.whl
+setuptools-58.5.2.tar.gz
+setuptools-58.5.3-py3-none-any.whl
+setuptools-58.5.3.tar.gz
+setuptools-59.0.1-py3-none-any.whl
+setuptools-59.0.1.tar.gz
+setuptools-59.1.0-py3-none-any.whl
+setuptools-59.1.0.tar.gz
+setuptools-59.1.1-py3-none-any.whl
+setuptools-59.1.1.tar.gz
+setuptools-59.2.0-py3-none-any.whl
+setuptools-59.2.0.tar.gz
+setuptools-59.3.0-py3-none-any.whl
+setuptools-59.3.0.tar.gz
+setuptools-59.4.0-py3-none-any.whl
+setuptools-59.4.0.tar.gz
+setuptools-59.5.0-py3-none-any.whl
+setuptools-59.5.0.tar.gz
+setuptools-59.6.0-py3-none-any.whl
+setuptools-59.6.0.tar.gz
+setuptools-59.7.0-py3-none-any.whl
+setuptools-59.7.0.tar.gz
+setuptools-59.8.0-py3-none-any.whl
+setuptools-59.8.0.tar.gz
+setuptools-60.0.0-py3-none-any.whl
+setuptools-60.0.0.tar.gz
+setuptools-60.0.1-py3-none-any.whl
+setuptools-60.0.1.tar.gz
+setuptools-60.0.2-py3-none-any.whl
+setuptools-60.0.2.tar.gz
+setuptools-60.0.3-py3-none-any.whl
+setuptools-60.0.3.tar.gz
+setuptools-60.0.4-py3-none-any.whl
+setuptools-60.0.4.tar.gz
+setuptools-60.0.5-py3-none-any.whl
+setuptools-60.0.5.tar.gz
+setuptools-60.1.0-py3-none-any.whl
+setuptools-60.1.0.tar.gz
+setuptools-60.1.1-py3-none-any.whl
+setuptools-60.1.1.tar.gz
+setuptools-60.2.0-py3-none-any.whl
+setuptools-60.2.0.tar.gz
+setuptools-60.3.0-py3-none-any.whl
+setuptools-60.3.0.tar.gz
+setuptools-60.3.1-py3-none-any.whl
+setuptools-60.3.1.tar.gz
+setuptools-60.4.0-py3-none-any.whl
+setuptools-60.4.0.tar.gz
+setuptools-60.5.0-py3-none-any.whl
+setuptools-60.5.0.tar.gz
+setuptools-60.6.0-py3-none-any.whl
+setuptools-60.6.0.tar.gz
+setuptools-60.7.0-py3-none-any.whl
+setuptools-60.7.0.tar.gz
+setuptools-60.7.1-py3-none-any.whl
+setuptools-60.7.1.tar.gz
+setuptools-60.8.0-py3-none-any.whl
+setuptools-60.8.0.tar.gz
+setuptools-60.8.1-py3-none-any.whl
+setuptools-60.8.1.tar.gz
+setuptools-60.8.2-py3-none-any.whl
+setuptools-60.8.2.tar.gz
+setuptools-60.9.0-py3-none-any.whl
+setuptools-60.9.0.tar.gz
+setuptools-60.9.1-py3-none-any.whl
+setuptools-60.9.1.tar.gz
+setuptools-60.9.2-py3-none-any.whl
+setuptools-60.9.2.tar.gz
+setuptools-60.9.3-py3-none-any.whl
+setuptools-60.9.3.tar.gz
+setuptools-60.10.0-py3-none-any.whl
+setuptools-60.10.0.tar.gz
+setuptools-61.0.0-py3-none-any.whl
+setuptools-61.0.0.tar.gz
+setuptools-61.1.0-py3-none-any.whl
+setuptools-61.1.0.tar.gz
+setuptools-61.1.1-py3-none-any.whl
+setuptools-61.1.1.tar.gz
+setuptools-61.2.0-py3-none-any.whl
+setuptools-61.2.0.tar.gz
+setuptools-61.3.0-py3-none-any.whl
+setuptools-61.3.0.tar.gz
+setuptools-61.3.1-py3-none-any.whl
+setuptools-61.3.1.tar.gz
+setuptools-62.0.0-py3-none-any.whl
+setuptools-62.0.0.tar.gz
+setuptools-62.1.0-py3-none-any.whl
+setuptools-62.1.0.tar.gz
+setuptools-62.2.0-py3-none-any.whl
+setuptools-62.2.0.tar.gz
+setuptools-62.3.0-py3-none-any.whl
+setuptools-62.3.0.tar.gz
+setuptools-62.3.1-py3-none-any.whl
+setuptools-62.3.1.tar.gz
+setuptools-62.3.2-py3-none-any.whl
+setuptools-62.3.2.tar.gz
+setuptools-62.3.3-py3-none-any.whl
+setuptools-62.3.3.tar.gz
+setuptools-62.3.4-py3-none-any.whl
+setuptools-62.3.4.tar.gz
+setuptools-62.4.0-py3-none-any.whl
+setuptools-62.4.0.tar.gz
+setuptools-62.5.0-py3-none-any.whl
+setuptools-62.5.0.tar.gz
+setuptools-62.6.0-py3-none-any.whl
+setuptools-62.6.0.tar.gz
+setuptools-63.0.0b1-py3-none-any.whl
+setuptools-63.0.0b1.tar.gz
+setuptools-63.0.0-py3-none-any.whl
+setuptools-63.0.0.tar.gz
+setuptools-63.1.0-py3-none-any.whl
+setuptools-63.1.0.tar.gz
+setuptools-63.2.0-py3-none-any.whl
+setuptools-63.2.0.tar.gz
+setuptools-63.3.0-py3-none-any.whl
+setuptools-63.3.0.tar.gz
+setuptools-63.4.0-py3-none-any.whl
+setuptools-63.4.0.tar.gz
+setuptools-63.4.1-py3-none-any.whl
+setuptools-63.4.1.tar.gz
+setuptools-63.4.2-py3-none-any.whl
+setuptools-63.4.2.tar.gz
+setuptools-63.4.3-py3-none-any.whl
+setuptools-63.4.3.tar.gz
+setuptools-64.0.0-py3-none-any.whl
+setuptools-64.0.0.tar.gz
+setuptools-64.0.1-py3-none-any.whl
+setuptools-64.0.1.tar.gz
+setuptools-64.0.2-py3-none-any.whl
+setuptools-64.0.2.tar.gz
+setuptools-64.0.3-py3-none-any.whl
+setuptools-64.0.3.tar.gz
+setuptools-65.0.0-py3-none-any.whl
+setuptools-65.0.0.tar.gz
+setuptools-65.0.1-py3-none-any.whl
+setuptools-65.0.1.tar.gz
+setuptools-65.0.2-py3-none-any.whl
+setuptools-65.0.2.tar.gz
+setuptools-65.1.0-py3-none-any.whl
+setuptools-65.1.0.tar.gz
+setuptools-65.1.1-py3-none-any.whl
+setuptools-65.1.1.tar.gz
+setuptools-65.2.0-py3-none-any.whl
+setuptools-65.2.0.tar.gz
+setuptools-65.3.0-py3-none-any.whl
+setuptools-65.3.0.tar.gz
+setuptools-65.4.0-py3-none-any.whl
+setuptools-65.4.0.tar.gz
+setuptools-65.4.1-py3-none-any.whl
+setuptools-65.4.1.tar.gz
+setuptools-65.5.0-py3-none-any.whl
+setuptools-65.5.0.tar.gz
+setuptools-65.5.1-py3-none-any.whl
+setuptools-65.5.1.tar.gz
+setuptools-65.6.0-py3-none-any.whl
+setuptools-65.6.0.tar.gz
+setuptools-65.6.1-py3-none-any.whl
+setuptools-65.6.1.tar.gz
+setuptools-65.6.2-py3-none-any.whl
+setuptools-65.6.2.tar.gz
+setuptools-65.6.3-py3-none-any.whl
+setuptools-65.6.3.tar.gz
+setuptools-65.7.0-py3-none-any.whl
+setuptools-65.7.0.tar.gz
+setuptools-66.0.0-py3-none-any.whl
+setuptools-66.0.0.tar.gz
+setuptools-66.1.0-py3-none-any.whl
+setuptools-66.1.0.tar.gz
+setuptools-66.1.1-py3-none-any.whl
+setuptools-66.1.1.tar.gz
+setuptools-67.0.0-py3-none-any.whl
+setuptools-67.0.0.tar.gz
+setuptools-67.1.0-py3-none-any.whl
+setuptools-67.1.0.tar.gz
+setuptools-67.2.0-py3-none-any.whl
+setuptools-67.2.0.tar.gz
+setuptools-67.3.1-py3-none-any.whl
+setuptools-67.3.1.tar.gz
+setuptools-67.3.2-py3-none-any.whl
+setuptools-67.3.2.tar.gz
+setuptools-67.3.3-py3-none-any.whl
+setuptools-67.3.3.tar.gz
+setuptools-67.4.0-py3-none-any.whl
+setuptools-67.4.0.tar.gz
+setuptools-67.5.0-py3-none-any.whl
+setuptools-67.5.0.tar.gz
+setuptools-67.5.1-py3-none-any.whl
+setuptools-67.5.1.tar.gz
+setuptools-67.6.0-py3-none-any.whl
+setuptools-67.6.0.tar.gz
+setuptools-67.6.1-py3-none-any.whl
+setuptools-67.6.1.tar.gz
+setuptools-67.7.0-py3-none-any.whl
+setuptools-67.7.0.tar.gz
+setuptools-67.7.1-py3-none-any.whl
+setuptools-67.7.1.tar.gz
+setuptools-67.7.2-py3-none-any.whl
+setuptools-67.7.2.tar.gz
+setuptools-67.8.0-py3-none-any.whl
+setuptools-67.8.0.tar.gz
+setuptools-68.0.0-py3-none-any.whl
+setuptools-68.0.0.tar.gz
+setuptools-68.1.0-py3-none-any.whl
+setuptools-68.1.0.tar.gz
+setuptools-68.1.2-py3-none-any.whl
+setuptools-68.1.2.tar.gz
+setuptools-68.2.0-py3-none-any.whl
+setuptools-68.2.0.tar.gz
+setuptools-68.2.1-py3-none-any.whl
+setuptools-68.2.1.tar.gz
+setuptools-68.2.2-py3-none-any.whl
+setuptools-68.2.2.tar.gz
+setuptools-69.0.0-py3-none-any.whl
+setuptools-69.0.0.tar.gz
+setuptools-69.0.1-py3-none-any.whl
+setuptools-69.0.1.tar.gz
+setuptools-69.0.2-py3-none-any.whl
+setuptools-69.0.2.tar.gz
+setuptools-69.0.3-py3-none-any.whl
+setuptools-69.0.3.tar.gz
+setuptools-69.1.0-py3-none-any.whl
+setuptools-69.1.0.tar.gz
+setuptools-69.1.1-py3-none-any.whl
+setuptools-69.1.1.tar.gz
+setuptools-69.2.0-py3-none-any.whl
+setuptools-69.2.0.tar.gz
+setuptools-69.3.0-py3-none-any.whl
+setuptools-69.3.tar.gz
+setuptools-69.3.1-py3-none-any.whl
+setuptools-69.3.1.tar.gz
+setuptools-69.4.0-py3-none-any.whl
+setuptools-69.4.tar.gz
+setuptools-69.4.1-py3-none-any.whl
+setuptools-69.4.1.tar.gz
+setuptools-69.4.2-py3-none-any.whl
+setuptools-69.4.2.tar.gz
+setuptools-69.5.0-py3-none-any.whl
+setuptools-69.5.0.tar.gz
+setuptools-69.5.1-py3-none-any.whl
+setuptools-69.5.1.tar.gz
+setuptools-70.0.0-py3-none-any.whl
+setuptools-70.0.0.tar.gz
+setuptools-70.1.0-py3-none-any.whl
+setuptools-70.1.0.tar.gz
+setuptools-70.1.1-py3-none-any.whl
+setuptools-70.1.1.tar.gz
+setuptools-70.2.0-py3-none-any.whl
+setuptools-70.2.0.tar.gz
+setuptools-70.3.0-py3-none-any.whl
+setuptools-70.3.0.tar.gz
+setuptools-71.0.0-py3-none-any.whl
+setuptools-71.0.0.tar.gz
+setuptools-71.0.1-py3-none-any.whl
+setuptools-71.0.1.tar.gz
+setuptools-71.0.2-py3-none-any.whl
+setuptools-71.0.2.tar.gz
+setuptools-71.0.3-py3-none-any.whl
+setuptools-71.0.3.tar.gz
+setuptools-71.0.4-py3-none-any.whl
+setuptools-71.0.4.tar.gz
+setuptools-71.1.0-py3-none-any.whl
+setuptools-71.1.0.tar.gz
+setuptools-72.0.0-py3-none-any.whl
+setuptools-72.0.0.tar.gz
+setuptools-72.1.0-py3-none-any.whl
+setuptools-72.1.0.tar.gz
+ + + \ No newline at end of file diff --git a/tests/tests_e3/pypi_data/simple/typing-extensions.html b/tests/tests_e3/pypi_data/simple/typing-extensions.html new file mode 100644 index 00000000..2f53b0a5 --- /dev/null +++ b/tests/tests_e3/pypi_data/simple/typing-extensions.html @@ -0,0 +1,103 @@ + + + + + Links for typing-extensions + + +

Links for typing-extensions

+typing_extensions-3.6.2-py2-none-any.whl
+typing_extensions-3.6.2-py3-none-any.whl
+typing_extensions-3.6.2.tar.gz
+typing_extensions-3.6.2.1-py2-none-any.whl
+typing_extensions-3.6.2.1-py3-none-any.whl
+typing_extensions-3.6.2.1.tar.gz
+typing_extensions-3.6.5-py2-none-any.whl
+typing_extensions-3.6.5-py3-none-any.whl
+typing_extensions-3.6.5.tar.gz
+typing_extensions-3.6.6-py2-none-any.whl
+typing_extensions-3.6.6-py3-none-any.whl
+typing_extensions-3.6.6.tar.gz
+typing_extensions-3.7.2-py2-none-any.whl
+typing_extensions-3.7.2-py3-none-any.whl
+typing_extensions-3.7.2.tar.gz
+typing_extensions-3.7.4-py2-none-any.whl
+typing_extensions-3.7.4-py3-none-any.whl
+typing_extensions-3.7.4.tar.gz
+typing_extensions-3.7.4.1-py2-none-any.whl
+typing_extensions-3.7.4.1-py3-none-any.whl
+typing_extensions-3.7.4.1.tar.gz
+typing_extensions-3.7.4.2-py2-none-any.whl
+typing_extensions-3.7.4.2-py3-none-any.whl
+typing_extensions-3.7.4.2.tar.gz
+typing_extensions-3.7.4.3-py2-none-any.whl
+typing_extensions-3.7.4.3-py3-none-any.whl
+typing_extensions-3.7.4.3.tar.gz
+typing_extensions-3.10.0.0-py2-none-any.whl
+typing_extensions-3.10.0.0-py3-none-any.whl
+typing_extensions-3.10.0.0.tar.gz
+typing_extensions-3.10.0.1-py2-none-any.whl
+typing_extensions-3.10.0.1-py3-none-any.whl
+typing_extensions-3.10.0.1.tar.gz
+typing_extensions-3.10.0.2-py2-none-any.whl
+typing_extensions-3.10.0.2-py3-none-any.whl
+typing_extensions-3.10.0.2.tar.gz
+typing_extensions-4.0.0-py3-none-any.whl
+typing_extensions-4.0.0.tar.gz
+typing_extensions-4.0.1-py3-none-any.whl
+typing_extensions-4.0.1.tar.gz
+typing_extensions-4.1.0-py3-none-any.whl
+typing_extensions-4.1.0.tar.gz
+typing_extensions-4.1.1-py3-none-any.whl
+typing_extensions-4.1.1.tar.gz
+typing_extensions-4.2.0-py3-none-any.whl
+typing_extensions-4.2.0.tar.gz
+typing_extensions-4.3.0-py3-none-any.whl
+typing_extensions-4.3.0.tar.gz
+typing_extensions-4.4.0-py3-none-any.whl
+typing_extensions-4.4.0.tar.gz
+typing_extensions-4.5.0-py3-none-any.whl
+typing_extensions-4.5.0.tar.gz
+typing_extensions-4.6.0-py3-none-any.whl
+typing_extensions-4.6.0.tar.gz
+typing_extensions-4.6.1-py3-none-any.whl
+typing_extensions-4.6.1.tar.gz
+typing_extensions-4.6.2-py3-none-any.whl
+typing_extensions-4.6.2.tar.gz
+typing_extensions-4.6.3-py3-none-any.whl
+typing_extensions-4.6.3.tar.gz
+typing_extensions-4.7.0rc1-py3-none-any.whl
+typing_extensions-4.7.0rc1.tar.gz
+typing_extensions-4.7.0-py3-none-any.whl
+typing_extensions-4.7.0.tar.gz
+typing_extensions-4.7.1-py3-none-any.whl
+typing_extensions-4.7.1.tar.gz
+typing_extensions-4.8.0rc1-py3-none-any.whl
+typing_extensions-4.8.0rc1.tar.gz
+typing_extensions-4.8.0-py3-none-any.whl
+typing_extensions-4.8.0.tar.gz
+typing_extensions-4.9.0rc1-py3-none-any.whl
+typing_extensions-4.9.0rc1.tar.gz
+typing_extensions-4.9.0-py3-none-any.whl
+typing_extensions-4.9.0.tar.gz
+typing_extensions-4.10.0rc1-py3-none-any.whl
+typing_extensions-4.10.0rc1.tar.gz
+typing_extensions-4.10.0-py3-none-any.whl
+typing_extensions-4.10.0.tar.gz
+typing_extensions-4.11.0rc1-py3-none-any.whl
+typing_extensions-4.11.0rc1.tar.gz
+typing_extensions-4.11.0-py3-none-any.whl
+typing_extensions-4.11.0.tar.gz
+typing_extensions-4.12.0a2-py3-none-any.whl
+typing_extensions-4.12.0a2.tar.gz
+typing_extensions-4.12.0rc1-py3-none-any.whl
+typing_extensions-4.12.0rc1.tar.gz
+typing_extensions-4.12.0-py3-none-any.whl
+typing_extensions-4.12.0.tar.gz
+typing_extensions-4.12.1-py3-none-any.whl
+typing_extensions-4.12.1.tar.gz
+typing_extensions-4.12.2-py3-none-any.whl
+typing_extensions-4.12.2.tar.gz
+ + + \ No newline at end of file diff --git a/tests/tests_e3/python/main_test.py b/tests/tests_e3/python/main_test.py index a5406ef0..a61bda24 100644 --- a/tests/tests_e3/python/main_test.py +++ b/tests/tests_e3/python/main_test.py @@ -42,7 +42,7 @@ def test_wheel(): mkdir(".cache") with PyPIClosure( - python3_version=10, + python3_version="3.10", platforms=[ "x86_64-linux", "aarch64-linux", @@ -62,11 +62,10 @@ def test_wheel(): pypi.add_requirement("src1!=0.4.2") pypi.add_requirement("src1~=1.0.0") assert len(pypi.file_closure()) == 2 - assert len(pypi.closure_as_requirements()) == 2 - assert len(pypi.closure()) == 2 + assert len(pypi.requirements_closure()) == 2 with PyPIClosure( - python3_version=10, + python3_version="3.10", platforms=[ "x86_64-linux", "aarch64-linux", @@ -80,8 +79,7 @@ def test_wheel(): pypi.add_requirement("src2==1.0.0") pypi.add_requirement("src1") assert len(pypi.file_closure()) == 2 - assert len(pypi.closure_as_requirements()) == 2 - assert len(pypi.closure()) == 2 + assert len(pypi.requirements_closure()) == 2 def test_pypi_closure_tool(): @@ -126,7 +124,7 @@ def test_star_requirements(): mkdir(".cache") with PyPIClosure( - python3_version=10, + python3_version="3.10", platforms=[ "x86_64-linux", ], @@ -135,11 +133,12 @@ def test_star_requirements(): ) as pypi: pypi.add_wheel(wheel1.path) pypi.add_wheel(wheel2.path) - with pytest.raises(PyPIError, match="Cannot satisfy constraint src1!=1.0.*"): - pypi.add_requirement("src2==1.0.0") + pypi.add_requirement("src2==1.0.0") + with pytest.raises(PyPIError, match="Impossible resolution"): + pypi.requirements_closure() with PyPIClosure( - python3_version=10, + python3_version="3.10", platforms=[ "x86_64-linux", ], @@ -149,7 +148,7 @@ def test_star_requirements(): pypi.add_wheel(wheel2.path) pypi.add_wheel(wheel3.path) pypi.add_requirement("src2==1.0.0") - assert len(pypi.closure()) == 2 + assert len(pypi.requirements_closure()) == 2 @pytest.mark.parametrize( @@ -169,7 +168,7 @@ def test_yanked(pypi_server, arguments, expected): with pypi_server: with PyPIClosure( - python3_version=11, + python3_version="3.11", platforms=[ "x86_64-linux", ], @@ -178,14 +177,13 @@ def test_yanked(pypi_server, arguments, expected): allowed_yanked=allowed_yanked, ) as pypi: if invalid_wheel: + pypi.add_requirement(invalid_wheel) + with pytest.raises( PyPIError, - match=( - "Cannot find latest version for 'setuptools-scm': " - "No more suitable version" - ), + match=("Impossible resolution"), ): - pypi.add_requirement(invalid_wheel) + pypi.requirements_closure() else: pypi.add_requirement("setuptools_scm >= 6.2, <= 8") all_filenames = [basename(f) for f in pypi.file_closure()]