From e063da357fd37065d0027e8041bb4e5ae1a4a8b6 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Mon, 19 Aug 2024 15:32:04 -0700 Subject: [PATCH 1/7] Support `--pre-resolved-dists` resolver. When building a PEX or creating a venv via `pex3 venv create` you can now tell Pex to use a set of pre-resolved distributions. This is similar to using `--no-pypi --find-links ...` except that: 1. Its roughly 3x faster since Pip is not asked to do any resolution. 2. It requires all the distributions specified form an already complete resolve. One way to obtain distributions that meet criteria 2 is to use `pip download -d ...` or `pip wheel -w ...` to pre-resolve the distributions you need. Closes #1907 --- pex/bin/pex.py | 34 +- pex/cli/commands/lock.py | 1 + pex/cli/commands/venv.py | 4 +- pex/common.py | 3 +- pex/dist_metadata.py | 83 +- pex/environment.py | 31 +- pex/jobs.py | 8 +- pex/pep_425.py | 5 +- pex/requirements.py | 2 +- pex/resolve/configured_resolve.py | 24 + pex/resolve/lock_resolver.py | 4 +- pex/resolve/locked_resolve.py | 51 +- pex/resolve/lockfile/create.py | 6 +- pex/resolve/pre_resolved_resolver.py | 216 ++ pex/resolve/resolved_requirement.py | 8 +- pex/resolve/resolver_configuration.py | 17 + pex/resolve/resolver_options.py | 80 +- pex/resolve/resolvers.py | 91 +- pex/resolver.py | 91 +- pex/targets.py | 39 +- testing/data/locks/devpi-server.lock.json | 2115 +++++++++++++++++ .../cli/commands/test_venv_create.py | 26 +- tests/integration/resolve/test_issue_1907.py | 155 ++ tests/integration/test_excludes.py | 34 + tests/integration/test_integration.py | 6 +- tests/integration/test_issue_1179.py | 17 +- tests/integration/test_overrides.py | 25 +- tests/resolve/test_resolver_options.py | 40 +- tests/test_dist_metadata.py | 2 +- tests/test_environment.py | 2 +- tests/test_resolver.py | 6 +- 31 files changed, 3003 insertions(+), 223 deletions(-) create mode 100644 pex/resolve/pre_resolved_resolver.py create mode 100644 testing/data/locks/devpi-server.lock.json create mode 100644 tests/integration/resolve/test_issue_1907.py diff --git a/pex/bin/pex.py b/pex/bin/pex.py index ebb462939..a22fdd70c 100755 --- a/pex/bin/pex.py +++ b/pex/bin/pex.py @@ -53,6 +53,7 @@ from pex.resolve.resolver_configuration import ( LockRepositoryConfiguration, PexRepositoryConfiguration, + PreResolvedConfiguration, ) from pex.resolve.resolver_options import create_pip_configuration from pex.resolve.resolvers import Unsatisfiable, sorted_requirements @@ -136,7 +137,9 @@ def configure_clp_pex_resolution(parser): ), ) - resolver_options.register(group, include_pex_repository=True, include_lock=True) + resolver_options.register( + group, include_pex_repository=True, include_lock=True, include_pre_resolved=True + ) group.add_argument( "--pex-path", @@ -1011,25 +1014,26 @@ def build_pex( DependencyConfiguration.from_pex_info(requirements_pex_info) ) + if isinstance(resolver_configuration, (LockRepositoryConfiguration, PreResolvedConfiguration)): + pip_configuration = resolver_configuration.pip_configuration + elif isinstance(resolver_configuration, PexRepositoryConfiguration): + # TODO(John Sirois): Consider finding a way to support custom --index and --find-links in + # this case. I.E.: I use a corporate index to build a PEX repository and now I want to + # build a --project PEX whose pyproject.toml build-system.requires should be resolved from + # that corporate index. + pip_configuration = try_( + finalize_resolve_config( + create_pip_configuration(options), targets=targets, context="--project building" + ) + ) + else: + pip_configuration = resolver_configuration + project_dependencies = OrderedSet() # type: OrderedSet[Requirement] with TRACER.timed( "Adding distributions built from local projects and collecting their requirements: " "{projects}".format(projects=" ".join(options.projects)) ): - if isinstance(resolver_configuration, LockRepositoryConfiguration): - pip_configuration = resolver_configuration.pip_configuration - elif isinstance(resolver_configuration, PexRepositoryConfiguration): - # TODO(John Sirois): Consider finding a way to support custom --index and --find-links in this case. - # I.E.: I use a corporate index to build a PEX repository and now I want to build a --project PEX - # whose pyproject.toml build-system.requires should be resolved from that corporate index. - pip_configuration = try_( - finalize_resolve_config( - create_pip_configuration(options), targets=targets, context="--project building" - ) - ) - else: - pip_configuration = resolver_configuration - projects = project.get_projects(options) built_projects = projects.build( targets=targets, diff --git a/pex/cli/commands/lock.py b/pex/cli/commands/lock.py index 2e48fa1c2..377454e4a 100644 --- a/pex/cli/commands/lock.py +++ b/pex/cli/commands/lock.py @@ -427,6 +427,7 @@ def _add_resolve_options(cls, parser): cls._create_resolver_options_group(parser), include_pex_repository=False, include_lock=False, + include_pre_resolved=False, ) @classmethod diff --git a/pex/cli/commands/venv.py b/pex/cli/commands/venv.py index 3e8625941..339136f92 100644 --- a/pex/cli/commands/venv.py +++ b/pex/cli/commands/venv.py @@ -110,7 +110,9 @@ def _add_create_arguments(cls, parser): ) installer_options.register(parser) target_options.register(parser, include_platforms=True) - resolver_options.register(parser, include_pex_repository=True, include_lock=True) + resolver_options.register( + parser, include_pex_repository=True, include_lock=True, include_pre_resolved=True + ) requirement_options.register(parser) @classmethod diff --git a/pex/common.py b/pex/common.py index 693c5f231..0799e4969 100644 --- a/pex/common.py +++ b/pex/common.py @@ -450,10 +450,11 @@ def can_write_dir(path): def touch(file): - # type: (Text) -> None + # type: (_Text) -> _Text """Equivalent of unix `touch path`.""" with safe_open(file, "a"): os.utime(file, None) + return file class Chroot(object): diff --git a/pex/dist_metadata.py b/pex/dist_metadata.py index 690058724..9140db0de 100644 --- a/pex/dist_metadata.py +++ b/pex/dist_metadata.py @@ -75,14 +75,65 @@ class InvalidMetadataError(MetadataError): """Indicates a metadata value that is invalid.""" +def is_tar_sdist(path): + # type: (Text) -> bool + # N.B.: PEP-625 (https://peps.python.org/pep-0625/) says sdists must use .tar.gz, but we + # have a known example of tar.bz2 in the wild in python-constraint 1.4.0 on PyPI: + # https://pypi.org/project/python-constraint/1.4.0/#files + # This probably all stems from the legacy `python setup.py sdist` as last described here: + # https://docs.python.org/3.11/distutils/sourcedist.html + # There was a move to reject exotic formats in PEP-527 in 2016 and the historical sdist + # formats appear to be listed here: https://peps.python.org/pep-0527/#file-extensions + # A query on the PyPI dataset shows: + # + # SELECT + # REGEXP_EXTRACT(path, r'\.([^.]+|tar\.[^.]+|tar)$') as extension, + # count(*) as count + # FROM `bigquery-public-data.pypi.distribution_metadata` + # group by extension + # order by count desc + # + # | extension | count | + # |-----------|---------| + # | whl | 6332494 | + # * | tar.gz | 5283102 | + # | egg | 135940 | + # * | zip | 108532 | + # | exe | 18452 | + # * | tar.bz2 | 3857 | + # | msi | 625 | + # | rpm | 603 | + # * | tgz | 226 | + # | dmg | 47 | + # | deb | 36 | + # * | tar.zip | 2 | + # * | ZIP | 1 | + return path.lower().endswith((".tar.gz", ".tgz", ".tar.bz2")) + + +def is_zip_sdist(path): + # type: (Text) -> bool + return path.lower().endswith(".zip") + + +def is_sdist(path): + # type: (Text) -> bool + return is_tar_sdist(path) or is_zip_sdist(path) + + +def is_wheel(path): + # type: (Text) -> bool + return path.lower().endswith(".whl") + + def _strip_sdist_path(sdist_path): # type: (Text) -> Optional[Text] - if not sdist_path.endswith((".tar.gz", ".tgz", ".tar.bz2", ".tbz2", ".tar.xz", ".txz", ".zip")): + if not is_sdist(sdist_path): return None sdist_basename = os.path.basename(sdist_path) filename, _ = os.path.splitext(sdist_basename) - if filename.endswith(".tar"): + if filename.lower().endswith(".tar"): filename, _ = os.path.splitext(filename) return filename @@ -194,8 +245,19 @@ def read_function(rel_path): ) +def _read_from_zip( + zip_location, # type: str + rel_path, # type: Text +): + # type: (...) -> bytes + with open_zip(zip_location) as zf: + return zf.read(rel_path) + + def find_wheel_metadata(location): # type: (Text) -> Optional[MetadataFiles] + + read_function = functools.partial(_read_from_zip, location) with open_zip(location) as zf: for name in zf.namelist(): if name.endswith("/"): @@ -218,11 +280,6 @@ def find_wheel_metadata(location): if dist_info_dir == head and tail != metadata_file_name: files.append(rel_path) - def read_function(rel_path): - # type: (Text) -> bytes - with open_zip(location) as zf: - return zf.read(rel_path) - return MetadataFiles( metadata=DistMetadataFile( type=MetadataType.DIST_INFO, @@ -330,7 +387,7 @@ def iter_metadata_files( location, MetadataType.DIST_INFO, "*.dist-info", "METADATA" ) ) - elif location.endswith(".whl") and zipfile.is_zipfile(location): + elif is_wheel(location) and zipfile.is_zipfile(location): metadata_files = find_wheel_metadata(location) if metadata_files: listing.append(metadata_files) @@ -341,13 +398,11 @@ def iter_metadata_files( ) ) elif MetadataType.PKG_INFO is metadata_type: - if location.endswith(".zip") and zipfile.is_zipfile(location): + if is_zip_sdist(location) and zipfile.is_zipfile(location): metadata_file = find_zip_sdist_metadata(location) if metadata_file: listing.append(MetadataFiles(metadata=metadata_file)) - elif location.endswith( - (".tar.gz", ".tgz", ".tar.bz2", ".tbz2", ".tar.xz", ".txz") - ) and tarfile.is_tarfile(location): + elif is_tar_sdist(location) and tarfile.is_tarfile(location): metadata_file = find_tar_sdist_metadata(location) if metadata_file: listing.append(MetadataFiles(metadata=metadata_file)) @@ -408,7 +463,7 @@ def from_filename(cls, path): # # The wheel filename convention is specified here: # https://www.python.org/dev/peps/pep-0427/#file-name-convention. - if path.endswith(".whl"): + if is_wheel(path): project_name, version, _ = os.path.basename(path).split("-", 2) return cls(project_name=project_name, version=version) @@ -903,7 +958,7 @@ def of(cls, location): # type: (Text) -> DistributionType.Value if os.path.isdir(location): return cls.INSTALLED - if location.endswith(".whl") and zipfile.is_zipfile(location): + if is_wheel(location) and zipfile.is_zipfile(location): return cls.WHEEL return cls.SDIST diff --git a/pex/environment.py b/pex/environment.py index 2452229fb..1b9317ab4 100644 --- a/pex/environment.py +++ b/pex/environment.py @@ -12,17 +12,18 @@ from pex import dist_metadata, pex_warnings, targets from pex.common import pluralize from pex.dependency_configuration import DependencyConfiguration -from pex.dist_metadata import Distribution, Requirement +from pex.dist_metadata import Distribution, Requirement, is_wheel from pex.fingerprinted_distribution import FingerprintedDistribution from pex.inherit_path import InheritPath from pex.interpreter import PythonInterpreter from pex.layout import ensure_installed, identify_layout from pex.orderedset import OrderedSet -from pex.pep_425 import CompatibilityTags, TagRank +from pex.pep_425 import TagRank from pex.pep_503 import ProjectName from pex.pex_info import PexInfo from pex.targets import Target from pex.third_party.packaging import specifiers +from pex.third_party.packaging.tags import Tag from pex.tracer import TRACER from pex.typing import TYPE_CHECKING @@ -139,7 +140,7 @@ def render_message(self, _target): @attr.s(frozen=True) class _TagMismatch(_UnrankedDistribution): - wheel_tags = attr.ib() # type: CompatibilityTags + wheel_tags = attr.ib() # type: Iterable[Tag] def render_message(self, target): # type: (Target) -> str @@ -332,8 +333,8 @@ def _update_candidate_distributions(self, distribution_iter): def _can_add(self, fingerprinted_dist): # type: (FingerprintedDistribution) -> Union[_RankedDistribution, _UnrankedDistribution] - filename, ext = os.path.splitext(os.path.basename(fingerprinted_dist.location)) - if ext.lower() != ".whl": + filename = os.path.basename(fingerprinted_dist.location) + if not is_wheel(filename): # This supports resolving pex's own vendored distributions which are vendored in a # directory with the project name (`pip/` for pip) and not the corresponding wheel name # (`pip-19.3.1-py2.py3-none-any.whl/` for pip). Pex only vendors universal wheels for @@ -341,23 +342,17 @@ def _can_add(self, fingerprinted_dist): return _RankedDistribution.highest_rank(fingerprinted_dist) try: - wheel_tags = CompatibilityTags.from_wheel(fingerprinted_dist.location) + wheel_eval = self._target.wheel_applies(fingerprinted_dist.distribution) except ValueError: return _InvalidWheelName(fingerprinted_dist, filename) - # There will be multiple parsed tags for compressed tag sets. Ensure we grab the parsed tag - # with highest rank from that expanded set. - best_match = self._target.supported_tags.best_match(wheel_tags) - if best_match is None: - return _TagMismatch(fingerprinted_dist, wheel_tags) + if not wheel_eval.best_match: + return _TagMismatch(fingerprinted_dist, wheel_eval.tags) + if not wheel_eval.applies: + assert wheel_eval.requires_python + return _PythonRequiresMismatch(fingerprinted_dist, wheel_eval.requires_python) - python_requires = dist_metadata.requires_python(fingerprinted_dist.distribution) - if python_requires and not self._target.requires_python_applies( - python_requires, source=fingerprinted_dist.distribution.as_requirement() - ): - return _PythonRequiresMismatch(fingerprinted_dist, python_requires) - - return _RankedDistribution(best_match.rank, fingerprinted_dist) + return _RankedDistribution(wheel_eval.best_match.rank, fingerprinted_dist) def activate(self): # type: () -> Iterable[Distribution] diff --git a/pex/jobs.py b/pex/jobs.py index bae04ccc6..bc75fe03b 100644 --- a/pex/jobs.py +++ b/pex/jobs.py @@ -744,10 +744,10 @@ def iter_map_parallel( # input_items.sort(key=costing_function, reverse=True) - # We want each of the job slots above to process MULTIPROCESSING_MIN_AVERAGE_LOAD on average in - # order to overcome multiprocessing overheads. Of course, if there are fewer available cores - # than that or the user has pinned max jobs lower, we clamp to that. Finally, we always want at - # least two slots to ensure we process input items in parallel. + # We want each of the job slots above to process MULTIPROCESSING_DEFAULT_MIN_AVERAGE_LOAD on + # average in order to overcome multiprocessing overheads. Of course, if there are fewer + # available cores than that or the user has pinned max jobs lower, we clamp to that. Finally, we + # always want at least two slots to ensure we process input items in parallel. pool_size = max(2, min(len(input_items) // min_average_load, _sanitize_max_jobs(max_jobs))) apply_function = functools.partial(_apply_function, function) diff --git a/pex/pep_425.py b/pex/pep_425.py index 2d5f35f4b..a32bf9ec3 100644 --- a/pex/pep_425.py +++ b/pex/pep_425.py @@ -6,6 +6,7 @@ import itertools import os.path +from pex.dist_metadata import is_wheel from pex.orderedset import OrderedSet from pex.rank import Rank from pex.third_party.packaging.tags import Tag, parse_tag @@ -56,14 +57,14 @@ class CompatibilityTags(object): @classmethod def from_wheel(cls, wheel): # type: (str) -> CompatibilityTags - wheel_stem, ext = os.path.splitext(os.path.basename(wheel)) - if ".whl" != ext: + if not is_wheel(wheel): raise ValueError( "Can only calculate wheel tags from a filename that ends in .whl per " "https://peps.python.org/pep-0427/#file-name-convention, given: {wheel!r}".format( wheel=wheel ) ) + wheel_stem, _ = os.path.splitext(os.path.basename(wheel)) # Wheel filename format: https://www.python.org/dev/peps/pep-0427/#file-name-convention # `{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl` wheel_components = wheel_stem.rsplit("-", 3) diff --git a/pex/requirements.py b/pex/requirements.py index 925f076be..8622557ab 100644 --- a/pex/requirements.py +++ b/pex/requirements.py @@ -285,7 +285,7 @@ class VCSScheme(object): def parse_scheme(scheme): - # type: (str) -> Optional[Union[str, ArchiveScheme.Value, VCSScheme]] + # type: (str) -> Union[str, ArchiveScheme.Value, VCSScheme] match = re.match( r""" ^ diff --git a/pex/resolve/configured_resolve.py b/pex/resolve/configured_resolve.py index 76790b7f7..412da57af 100644 --- a/pex/resolve/configured_resolve.py +++ b/pex/resolve/configured_resolve.py @@ -3,15 +3,18 @@ from __future__ import absolute_import +from pex.common import pluralize from pex.dependency_configuration import DependencyConfiguration from pex.pep_427 import InstallableType from pex.resolve.configured_resolver import ConfiguredResolver from pex.resolve.lock_resolver import resolve_from_lock from pex.resolve.pex_repository_resolver import resolve_from_pex +from pex.resolve.pre_resolved_resolver import resolve_from_dists from pex.resolve.requirement_configuration import RequirementConfiguration from pex.resolve.resolver_configuration import ( LockRepositoryConfiguration, PexRepositoryConfiguration, + PreResolvedConfiguration, ) from pex.resolve.resolvers import ResolveResult from pex.resolver import resolve as resolve_via_pip @@ -34,6 +37,7 @@ def resolve( dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration ): # type: (...) -> ResolveResult + if isinstance(resolver_configuration, LockRepositoryConfiguration): lock = try_(resolver_configuration.parse_lock()) with TRACER.timed( @@ -82,6 +86,26 @@ def resolve( result_type=result_type, dependency_configuration=dependency_configuration, ) + elif isinstance(resolver_configuration, PreResolvedConfiguration): + with TRACER.timed( + "Resolving requirements from {sdist_count} pre-resolved {sdists} and " + "{wheel_count} pre-resolved {wheels}.".format( + sdist_count=len(resolver_configuration.sdists), + sdists=pluralize(resolver_configuration.sdists, "sdist"), + wheel_count=len(resolver_configuration.wheels), + wheels=pluralize(resolver_configuration.wheels, "wheel"), + ) + ): + return resolve_from_dists( + targets=targets, + sdists=resolver_configuration.sdists, + wheels=resolver_configuration.wheels, + requirement_configuration=requirement_configuration, + compile=compile_pyc, + ignore_errors=ignore_errors, + result_type=result_type, + dependency_configuration=dependency_configuration, + ) else: with TRACER.timed("Resolving requirements."): return resolve_via_pip( diff --git a/pex/resolve/lock_resolver.py b/pex/resolve/lock_resolver.py index 91e2aa28c..475cc06d2 100644 --- a/pex/resolve/lock_resolver.py +++ b/pex/resolve/lock_resolver.py @@ -15,7 +15,7 @@ from pex.common import pluralize from pex.compatibility import cpu_count from pex.dependency_configuration import DependencyConfiguration -from pex.dist_metadata import Requirement +from pex.dist_metadata import Requirement, is_wheel from pex.network_configuration import NetworkConfiguration from pex.orderedset import OrderedSet from pex.pep_427 import InstallableType @@ -414,7 +414,7 @@ def resolve_from_lock( for resolved_subset in subset_result.subsets: for downloadable_artifact in resolved_subset.resolved.downloadable_artifacts: downloaded_artifact = downloaded_artifacts[downloadable_artifact] - if downloaded_artifact.path.endswith(".whl"): + if is_wheel(downloaded_artifact.path): install_requests.append( InstallRequest( target=resolved_subset.target, diff --git a/pex/resolve/locked_resolve.py b/pex/resolve/locked_resolve.py index eb50fd224..347414d23 100644 --- a/pex/resolve/locked_resolve.py +++ b/pex/resolve/locked_resolve.py @@ -10,7 +10,7 @@ from pex.common import pluralize from pex.dependency_configuration import DependencyConfiguration -from pex.dist_metadata import DistMetadata, Requirement +from pex.dist_metadata import DistMetadata, Requirement, is_sdist, is_wheel from pex.enum import Enum from pex.orderedset import OrderedSet from pex.pep_425 import CompatibilityTags, TagRank @@ -161,61 +161,16 @@ def __lt__(self, other): @attr.s(frozen=True, order=False) class FileArtifact(Artifact): - @staticmethod - def is_zip_sdist(path): - # type: (str) -> bool - - # N.B.: Windows sdists traditionally were released in zip format. - return path.endswith(".zip") - - @staticmethod - def is_tar_sdist(path): - # type: (str) -> bool - - # N.B.: PEP-625 (https://peps.python.org/pep-0625/) says sdists must use .tar.gz, but we - # have a known example of tar.bz2 in the wild in python-constraint 1.4.0 on PyPI: - # https://pypi.org/project/python-constraint/1.4.0/#files - # This probably all stems from the legacy `python setup.py sdist` as last described here: - # https://docs.python.org/3.11/distutils/sourcedist.html - # There was a move to reject exotic formats in PEP-527 in 2016 and the historical sdist - # formats appear to be listed here: https://peps.python.org/pep-0527/#file-extensions - # A query on the PyPI dataset shows: - # - # SELECT - # REGEXP_EXTRACT(path, r'\.([^.]+|tar\.[^.]+|tar)$') as extension, - # count(*) as count - # FROM `bigquery-public-data.pypi.distribution_metadata` - # group by extension - # order by count desc - # - # | extension | count | - # |-----------|---------| - # | whl | 6332494 | - # * | tar.gz | 5283102 | - # | egg | 135940 | - # * | zip | 108532 | - # | exe | 18452 | - # * | tar.bz2 | 3857 | - # | msi | 625 | - # | rpm | 603 | - # * | tgz | 226 | - # | dmg | 47 | - # | deb | 36 | - # * | tar.zip | 2 | - # * | ZIP | 1 | - # - return path.endswith((".tar.gz", ".tgz", ".tar.bz2")) - filename = attr.ib() # type: str @property def is_source(self): # type: () -> bool - return self.is_tar_sdist(self.filename) or self.is_zip_sdist(self.filename) + return is_sdist(self.filename) def parse_tags(self): # type: () -> Iterator[tags.Tag] - if self.filename.endswith(".whl"): + if is_wheel(self.filename): for tag in CompatibilityTags.from_wheel(self.filename): yield tag diff --git a/pex/resolve/lockfile/create.py b/pex/resolve/lockfile/create.py index 1ba05f381..1f1ba0dc2 100644 --- a/pex/resolve/lockfile/create.py +++ b/pex/resolve/lockfile/create.py @@ -14,7 +14,7 @@ from pex.build_system import pep_517 from pex.common import open_zip, pluralize, safe_mkdtemp from pex.dependency_configuration import DependencyConfiguration -from pex.dist_metadata import DistMetadata, ProjectNameAndVersion +from pex.dist_metadata import DistMetadata, ProjectNameAndVersion, is_tar_sdist, is_zip_sdist from pex.fetcher import URLFetcher from pex.jobs import Job, Retain, SpawnedJob, execute_parallel from pex.orderedset import OrderedSet @@ -155,10 +155,10 @@ def _prepare_project_directory(build_request): return target, project extract_dir = os.path.join(safe_mkdtemp(), "project") - if FileArtifact.is_zip_sdist(project): + if is_zip_sdist(project): with open_zip(project) as zf: zf.extractall(extract_dir) - elif FileArtifact.is_tar_sdist(project): + elif is_tar_sdist(project): with tarfile.open(project) as tf: tf.extractall(extract_dir) else: diff --git a/pex/resolve/pre_resolved_resolver.py b/pex/resolve/pre_resolved_resolver.py new file mode 100644 index 000000000..494dd60af --- /dev/null +++ b/pex/resolve/pre_resolved_resolver.py @@ -0,0 +1,216 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import hashlib +import os +from collections import defaultdict + +from pex.dependency_configuration import DependencyConfiguration +from pex.dist_metadata import Distribution, Requirement +from pex.fingerprinted_distribution import FingerprintedDistribution +from pex.interpreter import PythonInterpreter +from pex.jobs import iter_map_parallel +from pex.orderedset import OrderedSet +from pex.pep_427 import InstallableType +from pex.pep_503 import ProjectName +from pex.pip.tool import PackageIndexConfiguration +from pex.requirements import LocalProjectRequirement +from pex.resolve.configured_resolver import ConfiguredResolver +from pex.resolve.locked_resolve import Artifact, FileArtifact, LockedRequirement, LockedResolve +from pex.resolve.requirement_configuration import RequirementConfiguration +from pex.resolve.resolved_requirement import ArtifactURL, Fingerprint, Pin +from pex.resolve.resolver_configuration import PipConfiguration +from pex.resolve.resolvers import ( + ResolvedDistribution, + ResolveResult, + check_resolve, + sorted_requirements, +) +from pex.resolver import BuildAndInstallRequest, BuildRequest, InstallRequest +from pex.result import try_ +from pex.sorted_tuple import SortedTuple +from pex.targets import Targets +from pex.tracer import TRACER +from pex.typing import TYPE_CHECKING +from pex.util import CacheHelper + +if TYPE_CHECKING: + from typing import DefaultDict, Dict, Iterable, List + + +def _fingerprint_dist(dist_path): + # type: (str) -> FingerprintedDistribution + return FingerprintedDistribution( + distribution=Distribution.load(dist_path), + fingerprint=CacheHelper.hash(dist_path, hasher=hashlib.sha256), + ) + + +def resolve_from_dists( + targets, # type: Targets + sdists, # type: Iterable[str] + wheels, # type: Iterable[str] + requirement_configuration, # type: RequirementConfiguration + pip_configuration=PipConfiguration(), # type: PipConfiguration + compile=False, # type: bool + ignore_errors=False, # type: bool + result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value + dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration +): + # type: (...) -> ResolveResult + + unique_targets = targets.unique_targets() + + direct_requirements = requirement_configuration.parse_requirements( + pip_configuration.network_configuration + ) + local_projects = [] # type: List[LocalProjectRequirement] + for direct_requirement in direct_requirements: + if isinstance(direct_requirement, LocalProjectRequirement): + local_projects.append(direct_requirement) + + source_paths = [local_project.path for local_project in local_projects] + list( + sdists + ) # type: List[str] + with TRACER.timed("Fingerprinting pre-resolved wheels"): + fingerprinted_wheels = tuple( + iter_map_parallel( + inputs=wheels, + function=_fingerprint_dist, + max_jobs=pip_configuration.max_jobs, + costing_function=lambda whl: os.path.getsize(whl), + noun="wheel", + verb="fingerprint", + verb_past="fingerprinted", + ) + ) + + resolved_dists = [] # type: List[ResolvedDistribution] + resolve_installed_wheel_chroots = ( + fingerprinted_wheels and InstallableType.INSTALLED_WHEEL_CHROOT is result_type + ) + with TRACER.timed("Preparing pre-resolved distributions"): + if source_paths or resolve_installed_wheel_chroots: + package_index_configuration = PackageIndexConfiguration.create( + pip_version=pip_configuration.version, + resolver_version=pip_configuration.resolver_version, + indexes=pip_configuration.repos_configuration.indexes, + find_links=pip_configuration.repos_configuration.find_links, + network_configuration=pip_configuration.network_configuration, + password_entries=pip_configuration.repos_configuration.password_entries, + use_pip_config=pip_configuration.use_pip_config, + extra_pip_requirements=pip_configuration.extra_requirements, + ) + build_and_install = BuildAndInstallRequest( + build_requests=[ + BuildRequest.create(target=target, source_path=source_path) + for source_path in source_paths + for target in unique_targets + ], + install_requests=[ + InstallRequest( + target=target, wheel_path=wheel.location, fingerprint=wheel.fingerprint + ) + for wheel in fingerprinted_wheels + for target in unique_targets + ], + direct_requirements=direct_requirements, + package_index_configuration=package_index_configuration, + compile=compile, + build_configuration=pip_configuration.build_configuration, + verify_wheels=True, + pip_version=pip_configuration.version, + resolver=ConfiguredResolver(pip_configuration=pip_configuration), + dependency_configuration=dependency_configuration, + ) + resolved_dists.extend( + build_and_install.install_distributions( + ignore_errors=ignore_errors, + max_parallel_jobs=pip_configuration.max_jobs, + ) + if resolve_installed_wheel_chroots + else build_and_install.build_distributions( + ignore_errors=ignore_errors, + max_parallel_jobs=pip_configuration.max_jobs, + ) + ) + elif wheels: + direct_reqs_by_project_name = defaultdict( + list + ) # type: DefaultDict[ProjectName, List[Requirement]] + for parsed_req in direct_requirements: + assert not isinstance(parsed_req, LocalProjectRequirement) + direct_reqs_by_project_name[parsed_req.requirement.project_name].append( + parsed_req.requirement + ) + for wheel in fingerprinted_wheels: + direct_reqs = sorted_requirements(direct_reqs_by_project_name[wheel.project_name]) + for target in unique_targets: + resolved_dists.append( + ResolvedDistribution( + target=target, + fingerprinted_distribution=wheel, + direct_requirements=direct_reqs, + ) + ) + if not ignore_errors: + check_resolve(dependency_configuration, resolved_dists) + + with TRACER.timed("Sub-setting pre-resolved wheels"): + root_requirements = OrderedSet() # type: OrderedSet[Requirement] + locked_requirements = [] # type: List[LockedRequirement] + resolved_dist_by_file_artifact = {} # type: Dict[Artifact, ResolvedDistribution] + for resolved_dist in resolved_dists: + file_artifact = FileArtifact( + url=ArtifactURL.parse(resolved_dist.distribution.location), + fingerprint=Fingerprint(algorithm="sha256", hash=resolved_dist.fingerprint), + verified=True, + filename=os.path.basename(resolved_dist.distribution.location), + ) + dist_metadata = resolved_dist.distribution.metadata + locked_requirements.append( + LockedRequirement.create( + pin=Pin( + project_name=dist_metadata.project_name, + version=dist_metadata.version, + ), + artifact=file_artifact, + requires_python=dist_metadata.requires_python, + requires_dists=dist_metadata.requires_dists, + ) + ) + root_requirements.update(resolved_dist.direct_requirements) + resolved_dist_by_file_artifact[file_artifact] = resolved_dist + locked_resolve = LockedResolve( + locked_requirements=SortedTuple(locked_requirements), + platform_tag=PythonInterpreter.get().platform.tag, + ) # type: LockedResolve + + resolved_dists_subset = OrderedSet() # type: OrderedSet[ResolvedDistribution] + for target in unique_targets: + resolved = try_( + locked_resolve.resolve( + target=target, + requirements=root_requirements, + constraints=[ + constraint.requirement + for constraint in requirement_configuration.parse_constraints( + pip_configuration.network_configuration + ) + ], + transitive=True, + build_configuration=pip_configuration.build_configuration, + include_all_matches=False, + dependency_configuration=dependency_configuration, + ) + ) + for artifact in resolved.downloadable_artifacts: + resolved_dists_subset.add(resolved_dist_by_file_artifact[artifact.artifact]) + + return ResolveResult( + dependency_configuration=dependency_configuration, + distributions=tuple(resolved_dists_subset), + type=result_type, + ) diff --git a/pex/resolve/resolved_requirement.py b/pex/resolve/resolved_requirement.py index 5d8546055..eb63daa6c 100644 --- a/pex/resolve/resolved_requirement.py +++ b/pex/resolve/resolved_requirement.py @@ -7,7 +7,7 @@ from pex import hashing from pex.compatibility import url_unquote, urlparse -from pex.dist_metadata import ProjectNameAndVersion, Requirement +from pex.dist_metadata import ProjectNameAndVersion, Requirement, is_wheel from pex.hashing import HashlibHasher from pex.pep_440 import Version from pex.pep_503 import ProjectName @@ -91,7 +91,7 @@ class ArtifactURL(object): def parse(cls, url): # type: (str) -> ArtifactURL url_info = urlparse.urlparse(url) - scheme = parse_scheme(url_info.scheme) if url_info.scheme else None + scheme = parse_scheme(url_info.scheme) if url_info.scheme else "file" path = url_unquote(url_info.path) fingerprints = [] @@ -139,7 +139,7 @@ def parse(cls, url): raw_url = attr.ib(eq=False) # type: str download_url = attr.ib(eq=False) # type: str normalized_url = attr.ib() # type: str - scheme = attr.ib(eq=False) # type: Optional[Union[str, ArchiveScheme.Value, VCSScheme]] + scheme = attr.ib(eq=False) # type: Union[str, ArchiveScheme.Value, VCSScheme] path = attr.ib(eq=False) # type: str fragment_parameters = attr.ib(eq=False) # type: Mapping[str, Sequence[str]] fingerprints = attr.ib(eq=False) # type: Tuple[Fingerprint, ...] @@ -147,7 +147,7 @@ def parse(cls, url): @property def is_wheel(self): # type: () -> bool - return self.path.endswith(".whl") + return is_wheel(self.path) @property def fingerprint(self): diff --git a/pex/resolve/resolver_configuration.py b/pex/resolve/resolver_configuration.py index 61f788fcd..66f7983d2 100644 --- a/pex/resolve/resolver_configuration.py +++ b/pex/resolve/resolver_configuration.py @@ -220,3 +220,20 @@ def repos_configuration(self): def network_configuration(self): # type: () -> NetworkConfiguration return self.pip_configuration.network_configuration + + +@attr.s(frozen=True) +class PreResolvedConfiguration(object): + sdists = attr.ib() # type: Tuple[str, ...] + wheels = attr.ib() # type: Tuple[str, ...] + pip_configuration = attr.ib() # type: PipConfiguration + + @property + def repos_configuration(self): + # type: () -> ReposConfiguration + return self.pip_configuration.repos_configuration + + @property + def network_configuration(self): + # type: () -> NetworkConfiguration + return self.pip_configuration.network_configuration diff --git a/pex/resolve/resolver_options.py b/pex/resolve/resolver_options.py index 7ff37888d..967e43fd7 100644 --- a/pex/resolve/resolver_options.py +++ b/pex/resolve/resolver_options.py @@ -3,12 +3,13 @@ from __future__ import absolute_import +import glob import os from argparse import Action, ArgumentTypeError, Namespace, _ActionsContainer from pex import pex_warnings from pex.argparse import HandleBoolAction -from pex.dist_metadata import Requirement +from pex.dist_metadata import Requirement, is_sdist, is_wheel from pex.fetcher import initialize_ssl_context from pex.network_configuration import NetworkConfiguration from pex.orderedset import OrderedSet @@ -23,6 +24,7 @@ LockRepositoryConfiguration, PexRepositoryConfiguration, PipConfiguration, + PreResolvedConfiguration, ReposConfiguration, ResolverVersion, ) @@ -31,7 +33,7 @@ from pex.typing import TYPE_CHECKING, cast if TYPE_CHECKING: - from typing import Optional, Union + from typing import List, Optional, Union class _ManylinuxAction(Action): @@ -64,6 +66,7 @@ def register( parser, # type: _ActionsContainer include_pex_repository=False, # type: bool include_lock=False, # type: bool + include_pre_resolved=False, # type: bool ): # type: (...) -> None """Register resolver configuration options with the given parser. @@ -71,6 +74,8 @@ def register( :param parser: The parser to register resolver configuration options with. :param include_pex_repository: Whether to include the `--pex-repository` option. :param include_lock: Whether to include the `--lock` option. + :param include_pre_resolved: Whether to include the `--pre-resolved-dist` and + `--pre-resolved-dists` options. """ default_resolver_configuration = PipConfiguration() @@ -151,9 +156,15 @@ def register( help="Deprecated: No longer used.", ) - repository_choice = ( - parser.add_mutually_exclusive_group() if include_pex_repository and include_lock else parser - ) + repository_types = 0 + if include_pex_repository: + repository_types += 1 + if include_lock: + repository_types += 1 + if include_pre_resolved: + repository_types += 1 + + repository_choice = parser.add_mutually_exclusive_group() if repository_types > 1 else parser if include_pex_repository: repository_choice.add_argument( "--pex-repository", @@ -180,6 +191,23 @@ def register( ), ) register_lock_options(parser) + if include_pre_resolved: + repository_choice.add_argument( + "--pre-resolved-dist", + "--pre-resolved-dists", + dest="pre_resolved_dists", + metavar="FILE", + default=[], + type=str, + action="append", + help=( + "If a wheel, add it to the PEX. If an sdist, build wheels for the selected targets " + "and add them to the PEX. Otherwise, if a directory, add all the distributions " + "found in the given directory to the PEX, building wheels from any sdists first. " + "This option can be used to add a pre-resolved dependency set to a PEX. By " + "default, Pex will ensure the dependencies added form a closure." + ), + ) parser.add_argument( "--pre", @@ -481,7 +509,10 @@ class InvalidConfigurationError(Exception): if TYPE_CHECKING: ResolverConfiguration = Union[ - LockRepositoryConfiguration, PexRepositoryConfiguration, PipConfiguration + LockRepositoryConfiguration, + PexRepositoryConfiguration, + PipConfiguration, + PreResolvedConfiguration, ] @@ -494,26 +525,49 @@ def configure(options): """ pex_repository = getattr(options, "pex_repository", None) - lock = getattr(options, "lock", None) - if pex_repository and (options.indexes or options.find_links): - raise InvalidConfigurationError( - 'The "--pex-repository" option cannot be used together with the "--index" or ' - '"--find-links" options.' - ) - if pex_repository: + if options.indexes or options.find_links: + raise InvalidConfigurationError( + 'The "--pex-repository" option cannot be used together with the "--index" or ' + '"--find-links" options.' + ) return PexRepositoryConfiguration( pex_repository=pex_repository, network_configuration=create_network_configuration(options), transitive=options.transitive, ) + pip_configuration = create_pip_configuration(options) + lock = getattr(options, "lock", None) if lock: return LockRepositoryConfiguration( parse_lock=lambda: parse_lockfile(options, lock_file_path=lock), lock_file_path=lock, pip_configuration=pip_configuration, ) + + pre_resolved_dists = getattr(options, "pre_resolved_dists", None) + if pre_resolved_dists: + sdists = [] # type: List[str] + wheels = [] # type: List[str] + for dist_or_dir in pre_resolved_dists: + abs_dist_or_dir = os.path.expanduser(dist_or_dir) + dists = ( + [abs_dist_or_dir] + if os.path.isfile(abs_dist_or_dir) + else glob.glob(os.path.join(abs_dist_or_dir, "*")) + ) + for dist in dists: + if not os.path.isfile(dist): + continue + if is_wheel(dist): + wheels.append(dist) + elif is_sdist(dist): + sdists.append(dist) + return PreResolvedConfiguration( + sdists=tuple(sdists), wheels=tuple(wheels), pip_configuration=pip_configuration + ) + return pip_configuration diff --git a/pex/resolve/resolvers.py b/pex/resolve/resolvers.py index a2e12ebdb..68f679238 100644 --- a/pex/resolve/resolvers.py +++ b/pex/resolve/resolvers.py @@ -3,12 +3,17 @@ from __future__ import absolute_import +import itertools +import os from abc import abstractmethod +from collections import OrderedDict, defaultdict +from pex.common import pluralize from pex.dependency_configuration import DependencyConfiguration from pex.dist_metadata import Distribution, Requirement from pex.fingerprinted_distribution import FingerprintedDistribution from pex.pep_427 import InstallableType +from pex.pep_503 import ProjectName from pex.pip.version import PipVersionValue from pex.resolve.lockfile.model import Lockfile from pex.sorted_tuple import SortedTuple @@ -16,7 +21,7 @@ from pex.typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Iterable, Optional, Tuple + from typing import DefaultDict, Iterable, List, Optional, Tuple import attr # vendor:skip else: @@ -81,6 +86,90 @@ def with_direct_requirements(self, direct_requirements=None): ) +def check_resolve( + dependency_configuration, # type: DependencyConfiguration + resolved_distributions, # type: Iterable[ResolvedDistribution] +): + # type: (...) -> None + resolved_distributions_by_project_name = ( + OrderedDict() + ) # type: OrderedDict[ProjectName, List[ResolvedDistribution]] + for resolved_distribution in resolved_distributions: + resolved_distributions_by_project_name.setdefault( + resolved_distribution.distribution.metadata.project_name, [] + ).append(resolved_distribution) + + unsatisfied = defaultdict(list) # type: DefaultDict[Target, List[str]] + for resolved_distribution in itertools.chain.from_iterable( + resolved_distributions_by_project_name.values() + ): + dist = resolved_distribution.distribution + target = resolved_distribution.target + + for requirement in dist.requires(): + if dependency_configuration.excluded_by(requirement): + continue + requirement = ( + dependency_configuration.overridden_by(requirement, target=target) or requirement + ) + if not target.requirement_applies(requirement): + continue + + installed_requirement_dists = resolved_distributions_by_project_name.get( + requirement.project_name + ) + if not installed_requirement_dists: + unsatisfied[target].append( + "{dist} requires {requirement} but no version was resolved".format( + dist=dist.as_requirement(), requirement=requirement + ) + ) + else: + resolved_dists = [ + installed_requirement_dist.distribution + for installed_requirement_dist in installed_requirement_dists + ] + if not any( + ( + requirement.specifier.contains(resolved_dist.version, prereleases=True) + and target.wheel_applies(resolved_dist) + ) + for resolved_dist in resolved_dists + ): + unsatisfied[target].append( + "{dist} requires {requirement} but {count} incompatible {dists_were} " + "resolved:\n {dists}".format( + dist=dist, + requirement=requirement, + count=len(resolved_dists), + dists_were="dists were" if len(resolved_dists) > 1 else "dist was", + dists="\n ".join( + os.path.basename(resolved_dist.location) + for resolved_dist in resolved_dists + ), + ) + ) + + if unsatisfied: + unsatisfieds = [] + for target, missing in unsatisfied.items(): + unsatisfieds.append( + "{target} is not compatible with:\n {missing}".format( + target=target.render_description(), missing="\n ".join(missing) + ) + ) + raise Unsatisfiable( + "Failed to resolve compatible distributions for {count} {targets}:\n{failures}".format( + count=len(unsatisfieds), + targets=pluralize(unsatisfieds, "target"), + failures="\n".join( + "{index}: {failure}".format(index=index, failure=failure) + for index, failure in enumerate(unsatisfieds, start=1) + ), + ) + ) + + @attr.s(frozen=True) class ResolveResult(object): dependency_configuration = attr.ib() # type: DependencyConfiguration diff --git a/pex/resolver.py b/pex/resolver.py index 263d24370..47e495bd2 100644 --- a/pex/resolver.py +++ b/pex/resolver.py @@ -20,13 +20,12 @@ from pex.common import pluralize, safe_mkdir, safe_mkdtemp from pex.compatibility import url_unquote, urlparse from pex.dependency_configuration import DependencyConfiguration -from pex.dist_metadata import DistMetadata, Distribution, ProjectNameAndVersion, Requirement +from pex.dist_metadata import DistMetadata, Distribution, Requirement, is_wheel from pex.fingerprinted_distribution import FingerprintedDistribution from pex.jobs import Raise, SpawnedJob, execute_parallel, iter_map_parallel from pex.network_configuration import NetworkConfiguration from pex.orderedset import OrderedSet from pex.pep_376 import InstalledWheel -from pex.pep_425 import CompatibilityTags from pex.pep_427 import InstallableType, WheelError, install_wheel_chroot from pex.pep_503 import ProjectName from pex.pip.download_observer import DownloadObserver @@ -43,6 +42,7 @@ ResolveResult, Unsatisfiable, Untranslatable, + check_resolve, ) from pex.targets import LocalInterpreter, Target, Targets from pex.tracer import TRACER @@ -170,7 +170,7 @@ class DownloadResult(object): @staticmethod def _is_wheel(path): # type: (str) -> bool - return os.path.isfile(path) and path.endswith(".whl") + return is_wheel(path) and zipfile.is_zipfile(path) target = attr.ib() # type: Target download_dir = attr.ib() # type: str @@ -318,9 +318,8 @@ def finalize_build(self, check_compatible=True): ) wheel_path = wheels[0] if check_compatible and self.request.target.is_foreign: - wheel_tags = CompatibilityTags.from_wheel(wheel_path) - if not self.request.target.supported_tags.compatible_tags(wheel_tags): - project_name_and_version = ProjectNameAndVersion.from_filename(wheel_path) + wheel = Distribution.load(wheel_path) + if not self.request.target.wheel_applies(wheel): raise ValueError( "No pre-built wheel was available for {project_name} {version}.{eol}" "Successfully built the wheel {wheel} from the sdist {sdist} but it is not " @@ -328,8 +327,8 @@ def finalize_build(self, check_compatible=True): "You'll need to build a wheel from {sdist} on the foreign target platform and " "make it available to Pex via a `--find-links` repo or a custom " "`--index`.".format( - project_name=project_name_and_version.project_name, - version=project_name_and_version.version, + project_name=wheel.project_name, + version=wheel.version, eol=os.linesep, wheel=os.path.basename(wheel_path), sdist=os.path.basename(self.request.source_path), @@ -789,7 +788,7 @@ def _resolve_direct_file_deps( "The {wheel} wheel has a dependency on {url} which does not exist on this " "machine.".format(wheel=install_request.wheel_file, url=requirement.url) ) - if dist_path.endswith(".whl"): + if is_wheel(dist_path): to_install.add(InstallRequest.create(install_request.target, dist_path)) else: to_build.add(BuildRequest.create(install_request.target, dist_path)) @@ -873,7 +872,7 @@ def build_distributions( if not ignore_errors: with TRACER.timed("Checking build"): - self._check(wheels) + check_resolve(self._dependency_configuration, wheels) return direct_requirements.adjust(wheels) def install_distributions( @@ -946,77 +945,9 @@ def add_installation(install_result): if not ignore_errors: with TRACER.timed("Checking install"): - self._check(installations) + check_resolve(self._dependency_configuration, installations) return direct_requirements.adjust(installations) - def _check(self, resolved_distributions): - # type: (Iterable[ResolvedDistribution]) -> None - resolved_distributions_by_project_name = ( - OrderedDict() - ) # type: OrderedDict[ProjectName, List[ResolvedDistribution]] - for resolved_distribution in resolved_distributions: - resolved_distributions_by_project_name.setdefault( - resolved_distribution.distribution.metadata.project_name, [] - ).append(resolved_distribution) - - unsatisfied = [] - for resolved_distribution in itertools.chain.from_iterable( - resolved_distributions_by_project_name.values() - ): - dist = resolved_distribution.distribution - target = resolved_distribution.target - for requirement in dist.requires(): - if self._dependency_configuration.excluded_by(requirement): - continue - requirement = ( - self._dependency_configuration.overridden_by(requirement, target=target) - or requirement - ) - if not target.requirement_applies(requirement): - continue - - installed_requirement_dists = resolved_distributions_by_project_name.get( - requirement.project_name - ) - if not installed_requirement_dists: - unsatisfied.append( - "{dist} requires {requirement} but no version was resolved".format( - dist=dist.as_requirement(), requirement=requirement - ) - ) - else: - resolved_dists = [ - installed_requirement_dist.distribution - for installed_requirement_dist in installed_requirement_dists - ] - if not any( - requirement.specifier.contains(resolved_dist.version, prereleases=True) - for resolved_dist in resolved_dists - ): - unsatisfied.append( - "{dist} requires {requirement} but {count} incompatible {dists_were} " - "resolved: {dists}".format( - dist=dist.as_requirement(), - requirement=requirement, - count=len(resolved_dists), - dists_were="dists were" if len(resolved_dists) > 1 else "dist was", - dists=" ".join( - os.path.basename(resolved_dist.location) - for resolved_dist in resolved_dists - ), - ) - ) - - if unsatisfied: - raise Unsatisfiable( - "Failed to resolve compatible distributions:\n{failures}".format( - failures="\n".join( - "{index}: {failure}".format(index=index, failure=failure) - for index, failure in enumerate(unsatisfied, start=1) - ) - ) - ) - def _parse_reqs( requirements=None, # type: Optional[Iterable[str]] @@ -1265,7 +1196,7 @@ def _calculate_fingerprint(self): @property def is_wheel(self): - return self.path.endswith(".whl") and zipfile.is_zipfile(self.path) + return is_wheel(self.path) and zipfile.is_zipfile(self.path) @attr.s(frozen=True) diff --git a/pex/targets.py b/pex/targets.py index 37c02f229..49ad956f5 100644 --- a/pex/targets.py +++ b/pex/targets.py @@ -5,14 +5,15 @@ import os -from pex.dist_metadata import Requirement +from pex.dist_metadata import Distribution, Requirement from pex.interpreter import PythonInterpreter, calculate_binary_name from pex.orderedset import OrderedSet -from pex.pep_425 import CompatibilityTags +from pex.pep_425 import CompatibilityTags, RankedTag from pex.pep_508 import MarkerEnvironment from pex.platforms import Platform from pex.result import Error from pex.third_party.packaging.specifiers import SpecifierSet +from pex.third_party.packaging.tags import Tag from pex.typing import TYPE_CHECKING, cast if TYPE_CHECKING: @@ -27,6 +28,21 @@ class RequiresPythonError(Exception): """Indicates the impossibility of evaluating Requires-Python metadata.""" +@attr.s(frozen=True) +class WheelEvaluation(object): + tags = attr.ib() # type: Tuple[Tag, ...] + best_match = attr.ib() # type: Optional[RankedTag] + requires_python = attr.ib() # type: Optional[SpecifierSet] + applies = attr.ib() # type: bool + + def __bool__(self): + # type: () -> bool + return self.applies + + # N.B.: For Python 2.7. + __nonzero__ = __bool__ + + @attr.s(frozen=True) class Target(object): id = attr.ib() # type: str @@ -153,6 +169,25 @@ def requirement_applies( return False + def wheel_applies(self, wheel): + # type: (Distribution) -> WheelEvaluation + wheel_tags = CompatibilityTags.from_wheel(wheel.location) + ranked_tag = self.supported_tags.best_match(wheel_tags) + return WheelEvaluation( + tags=tuple(wheel_tags), + best_match=ranked_tag, + requires_python=wheel.metadata.requires_python, + applies=( + ranked_tag is not None + and ( + not wheel.metadata.requires_python + or self.requires_python_applies( + wheel.metadata.requires_python, source=wheel.location + ) + ) + ), + ) + def __str__(self): # type: () -> str return str(self.platform.tag) diff --git a/testing/data/locks/devpi-server.lock.json b/testing/data/locks/devpi-server.lock.json new file mode 100644 index 000000000..b1cf728cf --- /dev/null +++ b/testing/data/locks/devpi-server.lock.json @@ -0,0 +1,2115 @@ +{ + "allow_builds": true, + "allow_prereleases": false, + "allow_wheels": true, + "build_isolation": true, + "constraints": [], + "excluded": [], + "locked_resolves": [ + { + "locked_requirements": [ + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7", + "url": "https://files.pythonhosted.org/packages/7b/a2/10639a79341f6c019dedc95bd48a4928eed9f1d1197f4c04f546fc7ae0ff/anyio-4.4.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", + "url": "https://files.pythonhosted.org/packages/e6/e3/c4c8d473d6780ef1853d630d581f70d655b4f8d7553c6997958c283039a2/anyio-4.4.0.tar.gz" + } + ], + "project_name": "anyio", + "requires_dists": [ + "Sphinx>=7; extra == \"doc\"", + "anyio[trio]; extra == \"test\"", + "coverage[toml]>=7; extra == \"test\"", + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "exceptiongroup>=1.2.0; extra == \"test\"", + "hypothesis>=4.0; extra == \"test\"", + "idna>=2.8", + "packaging; extra == \"doc\"", + "psutil>=5.9; extra == \"test\"", + "pytest-mock>=3.6.1; extra == \"test\"", + "pytest>=7.0; extra == \"test\"", + "sniffio>=1.1", + "sphinx-autodoc-typehints>=1.2.0; extra == \"doc\"", + "sphinx-rtd-theme; extra == \"doc\"", + "trio>=0.23; extra == \"trio\"", + "trustme; extra == \"test\"", + "typing-extensions>=4.1; python_version < \"3.11\"", + "uvloop>=0.17; (platform_python_implementation == \"CPython\" and platform_system != \"Windows\") and extra == \"test\"" + ], + "requires_python": ">=3.8", + "version": "4.4.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", + "url": "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", + "url": "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz" + } + ], + "project_name": "argon2-cffi", + "requires_dists": [ + "argon2-cffi-bindings", + "argon2-cffi[tests,typing]; extra == \"dev\"", + "furo; extra == \"docs\"", + "hypothesis; extra == \"tests\"", + "mypy; extra == \"typing\"", + "myst-parser; extra == \"docs\"", + "pytest; extra == \"tests\"", + "sphinx-copybutton; extra == \"docs\"", + "sphinx-notfound-page; extra == \"docs\"", + "sphinx; extra == \"docs\"", + "tox>4; extra == \"dev\"", + "typing-extensions; python_version < \"3.8\"" + ], + "requires_python": ">=3.7", + "version": "23.1.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", + "url": "https://files.pythonhosted.org/packages/ee/0f/a2260a207f21ce2ff4cad00a417c31597f08eafb547e00615bcbf403d8ea/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", + "url": "https://files.pythonhosted.org/packages/2e/f1/48888db30b6a4a0c78ab7bc7444058a1135b223b6a2a5f2ac7d6780e7443/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", + "url": "https://files.pythonhosted.org/packages/34/da/d105a3235ae86c1c1a80c1e9c46953e6e53cc8c4c61fb3c5ac8a39bbca48/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", + "url": "https://files.pythonhosted.org/packages/43/f3/20bc53a6e50471dfea16a63dc9b69d2a9ec78fd2b9532cc25f8317e121d9/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", + "url": "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", + "url": "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", + "url": "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", + "url": "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", + "url": "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", + "url": "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", + "url": "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", + "url": "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", + "url": "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + } + ], + "project_name": "argon2-cffi-bindings", + "requires_dists": [ + "cffi>=1.0.1", + "cogapp; extra == \"dev\"", + "pre-commit; extra == \"dev\"", + "pytest; extra == \"dev\"", + "pytest; extra == \"tests\"", + "wheel; extra == \"dev\"" + ], + "requires_python": ">=3.6", + "version": "21.2.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", + "url": "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "url": "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz" + } + ], + "project_name": "attrs", + "requires_dists": [ + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"benchmark\"", + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"cov\"", + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"dev\"", + "cloudpickle; platform_python_implementation == \"CPython\" and extra == \"tests\"", + "cogapp; extra == \"docs\"", + "coverage[toml]>=5.3; extra == \"cov\"", + "furo; extra == \"docs\"", + "hypothesis; extra == \"benchmark\"", + "hypothesis; extra == \"cov\"", + "hypothesis; extra == \"dev\"", + "hypothesis; extra == \"tests\"", + "importlib-metadata; python_version < \"3.8\"", + "mypy>=1.11.1; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\") and extra == \"benchmark\"", + "mypy>=1.11.1; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\") and extra == \"cov\"", + "mypy>=1.11.1; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\") and extra == \"dev\"", + "mypy>=1.11.1; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\") and extra == \"tests\"", + "mypy>=1.11.1; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\") and extra == \"tests-mypy\"", + "myst-parser; extra == \"docs\"", + "pre-commit; extra == \"dev\"", + "pympler; extra == \"benchmark\"", + "pympler; extra == \"cov\"", + "pympler; extra == \"dev\"", + "pympler; extra == \"tests\"", + "pytest-codspeed; extra == \"benchmark\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\") and extra == \"benchmark\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\") and extra == \"cov\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\") and extra == \"dev\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\") and extra == \"tests\"", + "pytest-mypy-plugins; (platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\") and extra == \"tests-mypy\"", + "pytest-xdist[psutil]; extra == \"benchmark\"", + "pytest-xdist[psutil]; extra == \"cov\"", + "pytest-xdist[psutil]; extra == \"dev\"", + "pytest-xdist[psutil]; extra == \"tests\"", + "pytest>=4.3.0; extra == \"benchmark\"", + "pytest>=4.3.0; extra == \"cov\"", + "pytest>=4.3.0; extra == \"dev\"", + "pytest>=4.3.0; extra == \"tests\"", + "sphinx-notfound-page; extra == \"docs\"", + "sphinx; extra == \"docs\"", + "sphinxcontrib-towncrier; extra == \"docs\"", + "towncrier<24.7; extra == \"docs\"" + ], + "requires_python": ">=3.7", + "version": "24.2.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "url": "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", + "url": "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz" + } + ], + "project_name": "certifi", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "2024.8.30" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "url": "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "url": "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "url": "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "url": "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "url": "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "url": "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "url": "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "url": "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "url": "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "url": "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "url": "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "url": "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "url": "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "url": "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "url": "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "url": "https://files.pythonhosted.org/packages/48/08/15bf6b43ae9bd06f6b00ad8a91f5a8fe1069d4c9fab550a866755402724e/cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "url": "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "url": "https://files.pythonhosted.org/packages/53/93/7e547ab4105969cc8c93b38a667b82a835dd2cc78f3a7dad6130cfd41e1d/cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "url": "https://files.pythonhosted.org/packages/56/c4/a308f2c332006206bb511de219efeff090e9d63529ba0a77aae72e82248b/cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "url": "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "url": "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "url": "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "url": "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "url": "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "url": "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "url": "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "url": "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "url": "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "url": "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "url": "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "url": "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "url": "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "url": "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "url": "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "url": "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "url": "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "url": "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "url": "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "url": "https://files.pythonhosted.org/packages/bb/19/b51af9f4a4faa4a8ac5a0e5d5c2522dcd9703d07fac69da34a36c4d960d3/cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "url": "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "url": "https://files.pythonhosted.org/packages/c2/5b/f1523dd545f92f7df468e5f653ffa4df30ac222f3c884e51e139878f1cb5/cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "url": "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "url": "https://files.pythonhosted.org/packages/ca/5b/b63681518265f2f4060d2b60755c1c77ec89e5e045fc3773b72735ddaad5/cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "url": "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "url": "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "url": "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "url": "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "url": "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "url": "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "url": "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "url": "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", + "url": "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "url": "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "url": "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "url": "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + } + ], + "project_name": "cffi", + "requires_dists": [ + "pycparser" + ], + "requires_python": ">=3.8", + "version": "1.17.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "url": "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "url": "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "url": "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "url": "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "url": "https://files.pythonhosted.org/packages/13/82/83c188028b6f38d39538442dd127dc794c602ae6d45d66c469f4063a4c30/charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "url": "https://files.pythonhosted.org/packages/16/ea/a9e284aa38cccea06b7056d4cbc7adf37670b1f8a668a312864abf1ff7c6/charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "url": "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "url": "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "url": "https://files.pythonhosted.org/packages/1f/8d/33c860a7032da5b93382cbe2873261f81467e7b37f4ed91e25fed62fd49b/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "url": "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "url": "https://files.pythonhosted.org/packages/2a/9d/a6d15bd1e3e2914af5955c8eb15f4071997e7078419328fee93dfd497eb7/charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "url": "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "url": "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "url": "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "url": "https://files.pythonhosted.org/packages/33/95/ef68482e4a6adf781fae8d183fb48d6f2be8facb414f49c90ba6a5149cd1/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "url": "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "url": "https://files.pythonhosted.org/packages/34/2a/f392457d45e24a0c9bfc012887ed4f3c54bf5d4d05a5deb970ffec4b7fc0/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "url": "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "url": "https://files.pythonhosted.org/packages/3d/09/d82fe4a34c5f0585f9ea1df090e2a71eb9bb1e469723053e1ee9f57c16f3/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "url": "https://files.pythonhosted.org/packages/3d/85/5b7416b349609d20611a64718bed383b9251b5a601044550f0c8983b8900/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "url": "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "url": "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "url": "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "url": "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "url": "https://files.pythonhosted.org/packages/44/80/b339237b4ce635b4af1c73742459eee5f97201bd92b2371c53e11958392e/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "url": "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "url": "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "url": "https://files.pythonhosted.org/packages/51/fd/0ee5b1c2860bb3c60236d05b6e4ac240cf702b67471138571dad91bcfed8/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "url": "https://files.pythonhosted.org/packages/53/cd/aa4b8a4d82eeceb872f83237b2d27e43e637cac9ffaef19a1321c3bafb67/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561", + "url": "https://files.pythonhosted.org/packages/54/7f/cad0b328759630814fcf9d804bfabaf47776816ad4ef2e9938b7e1123d04/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "url": "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "url": "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "url": "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "url": "https://files.pythonhosted.org/packages/66/fe/c7d3da40a66a6bf2920cce0f436fa1f62ee28aaf92f412f0bf3b84c8ad6c/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "url": "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "url": "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "url": "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "url": "https://files.pythonhosted.org/packages/79/66/8946baa705c588521afe10b2d7967300e49380ded089a62d38537264aece/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "url": "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "url": "https://files.pythonhosted.org/packages/81/b2/160893421adfa3c45554fb418e321ed342bb10c0a4549e855b2b2a3699cb/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "url": "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "url": "https://files.pythonhosted.org/packages/98/69/5d8751b4b670d623aa7a47bef061d69c279e9f922f6705147983aa76c3ce/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "url": "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "url": "https://files.pythonhosted.org/packages/9e/ef/cd47a63d3200b232792e361cd67530173a09eb011813478b1c0fb8aa7226/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "url": "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "url": "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "url": "https://files.pythonhosted.org/packages/a8/6f/4ff299b97da2ed6358154b6eb3a2db67da2ae204e53d205aacb18a7e4f34/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "url": "https://files.pythonhosted.org/packages/b3/c1/ebca8e87c714a6a561cfee063f0655f742e54b8ae6e78151f60ba8708b3a/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "url": "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "url": "https://files.pythonhosted.org/packages/bd/28/7ea29e73eea52c7e15b4b9108d0743fc9e4cc2cdb00d275af1df3d46d360/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "url": "https://files.pythonhosted.org/packages/be/4d/9e370f8281cec2fcc9452c4d1ac513324c32957c5f70c73dd2fa8442a21a/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "url": "https://files.pythonhosted.org/packages/c2/65/52aaf47b3dd616c11a19b1052ce7fa6321250a7a0b975f48d8c366733b9f/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "url": "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "url": "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "url": "https://files.pythonhosted.org/packages/d1/2f/0d1efd07c74c52b6886c32a3b906fb8afd2fecf448650e73ecb90a5a27f1/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "url": "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "url": "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "url": "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "url": "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "url": "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "url": "https://files.pythonhosted.org/packages/e1/9c/60729bf15dc82e3aaf5f71e81686e42e50715a1399770bcde1a9e43d09db/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl" + }, + { + "algorithm": "sha256", + "hash": "f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "url": "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "url": "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "url": "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "url": "https://files.pythonhosted.org/packages/ef/d4/a1d72a8f6aa754fdebe91b848912025d30ab7dced61e9ed8aabbf791ed65/charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "url": "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" + }, + { + "algorithm": "sha256", + "hash": "c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "url": "https://files.pythonhosted.org/packages/f7/9d/bcf4a449a438ed6f19790eee543a86a740c77508fbc5ddab210ab3ba3a9a/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl" + } + ], + "project_name": "charset-normalizer", + "requires_dists": [], + "requires_python": ">=3.7.0", + "version": "3.3.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", + "url": "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "url": "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz" + } + ], + "project_name": "defusedxml", + "requires_dists": [], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "version": "0.7.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "8a83b788ac821ade1e348d571db083575c5c5d92ba0932a3a8f75c6f8dde4d88", + "url": "https://files.pythonhosted.org/packages/7c/f8/923116cb379060431a8df6c6c561b78734e2fc9e933e0365e06f8622aaf4/devpi_common-4.0.4-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "235a0a9a45c96e54c60ba6ba2f77d856cf90f1a69c1bee949887e9edc03a41cc", + "url": "https://files.pythonhosted.org/packages/4e/9a/3522931638d784eae9c854a023b86b6455db289ca823ab6eb257af433d4a/devpi_common-4.0.4.tar.gz" + } + ], + "project_name": "devpi-common", + "requires_dists": [ + "lazy", + "packaging-legacy", + "requests>=2.3.0" + ], + "requires_python": ">=3.7", + "version": "4.0.4" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c4a74cad8bb45c0a4f097ec22b0575265121b531747aaa72a1d5a0b471a60dab", + "url": "https://files.pythonhosted.org/packages/72/02/bcf6914dcb29e165ef105ea25f9939fe5c9574083912c6ddd2f5bfce1870/devpi_server-6.12.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "31e916f06562d6c29edb926e01a94800666afe6333a84c95c8e11285549212d1", + "url": "https://files.pythonhosted.org/packages/3d/09/9d5c3dfc33d9e6e37e6f393744f0558aaaad4abb45b33f7c789a849eee41/devpi_server-6.12.0.tar.gz" + } + ], + "project_name": "devpi-server", + "requires_dists": [ + "argon2-cffi", + "attrs>=21.3.0", + "defusedxml", + "devpi-common<5,>3.6.0", + "httpx", + "itsdangerous>=0.24", + "lazy", + "passlib[argon2]", + "platformdirs", + "pluggy<2.0,>=0.6.0", + "py>=1.4.23", + "pyramid>=2", + "repoze.lru>=0.6", + "ruamel.yaml", + "strictyaml", + "waitress>=1.0.1" + ], + "requires_python": ">=3.7", + "version": "6.12.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", + "url": "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", + "url": "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz" + } + ], + "project_name": "exceptiongroup", + "requires_dists": [ + "pytest>=6; extra == \"test\"" + ], + "requires_python": ">=3.7", + "version": "1.2.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", + "url": "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "url": "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz" + } + ], + "project_name": "h11", + "requires_dists": [ + "typing-extensions; python_version < \"3.8\"" + ], + "requires_python": ">=3.7", + "version": "0.14.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5", + "url": "https://files.pythonhosted.org/packages/78/d4/e5d7e4f2174f8a4d63c8897d79eb8fe2503f7ecc03282fee1fa2719c2704/httpcore-1.0.5-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", + "url": "https://files.pythonhosted.org/packages/17/b0/5e8b8674f8d203335a62fdfcfa0d11ebe09e23613c3391033cbba35f7926/httpcore-1.0.5.tar.gz" + } + ], + "project_name": "httpcore", + "requires_dists": [ + "anyio<5.0,>=4.0; extra == \"asyncio\"", + "certifi", + "h11<0.15,>=0.13", + "h2<5,>=3; extra == \"http2\"", + "socksio==1.*; extra == \"socks\"", + "trio<0.26.0,>=0.22.0; extra == \"trio\"" + ], + "requires_python": ">=3.8", + "version": "1.0.5" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", + "url": "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", + "url": "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz" + } + ], + "project_name": "httpx", + "requires_dists": [ + "anyio", + "brotli; platform_python_implementation == \"CPython\" and extra == \"brotli\"", + "brotlicffi; platform_python_implementation != \"CPython\" and extra == \"brotli\"", + "certifi", + "click==8.*; extra == \"cli\"", + "h2<5,>=3; extra == \"http2\"", + "httpcore==1.*", + "idna", + "pygments==2.*; extra == \"cli\"", + "rich<14,>=10; extra == \"cli\"", + "sniffio", + "socksio==1.*; extra == \"socks\"", + "zstandard>=0.18.0; extra == \"zstd\"" + ], + "requires_python": ">=3.8", + "version": "0.27.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "e872b959f09d90be5fb615bd2e62de89a0b57efc037bdf9637fb09cdf8552b19", + "url": "https://files.pythonhosted.org/packages/86/7d/3888833e4f5ea56af4a9935066ec09a83228e533d7b8877f65889d706ee4/hupper-1.12.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "06bf54170ff4ecf4c84ad5f188dee3901173ab449c2608ad05b9bfd6b13e32eb", + "url": "https://files.pythonhosted.org/packages/bd/e6/bb064537288eee2be97f3e0fcad8e7242bc5bbe9664ae57c7d29b3fa18c2/hupper-1.12.1.tar.gz" + } + ], + "project_name": "hupper", + "requires_dists": [ + "Sphinx; extra == \"docs\"", + "mock; extra == \"testing\"", + "pylons-sphinx-themes; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"", + "setuptools; extra == \"docs\"", + "watchdog; extra == \"docs\"", + "watchdog; extra == \"testing\"" + ], + "requires_python": ">=3.7", + "version": "1.12.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "url": "https://files.pythonhosted.org/packages/22/7e/d71db821f177828df9dea8c42ac46473366f191be53080e552e628aad991/idna-3.8-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603", + "url": "https://files.pythonhosted.org/packages/e8/ac/e349c5e6d4543326c6883ee9491e3921e0d07b55fdf3cce184b40d63e72a/idna-3.8.tar.gz" + } + ], + "project_name": "idna", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "3.8" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "url": "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", + "url": "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz" + } + ], + "project_name": "itsdangerous", + "requires_dists": [], + "requires_python": ">=3.8", + "version": "2.2.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "449375c125c7acac6b7a93f71b8e7ccb06546c37b161613f92d2d3981f793244", + "url": "https://files.pythonhosted.org/packages/11/ae/3ae578fc22dc9c5f60ddcb5c254fe808d45ee7b4cd03315245caf5db6a47/lazy-1.6-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "7127324ec709e8324f08cb4611c1abe01776bda53bb9ce68dc5dfa46ca0ed3e9", + "url": "https://files.pythonhosted.org/packages/3c/e6/704c32da169b023a9ac86d116f5433b42d02b4afeda24c9400a69b3530e5/lazy-1.6.tar.gz" + } + ], + "project_name": "lazy", + "requires_dists": [ + "mypy; extra == \"mypy\"", + "sphinx-rtd-theme==1.0.0; extra == \"docs\"", + "sphinx==5.3.0; extra == \"docs\"" + ], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7", + "version": "1.6" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", + "url": "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "url": "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz" + } + ], + "project_name": "packaging", + "requires_dists": [], + "requires_python": ">=3.8", + "version": "24.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "6cd21cd283c09409349bccc10bb55bfd837b4aab86a7b0f87bfcb8dd9831a8a3", + "url": "https://files.pythonhosted.org/packages/64/3a/cca5260a6e346027495775bef30869acce88d6f72f69ad7c27a0ba87ec95/packaging_legacy-23.0.post0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "c974a42291a77112313f0198b87ad96e07a3c357295d572560a4b9c368f7d9db", + "url": "https://files.pythonhosted.org/packages/f8/31/3a2fe3f5fc01a0671ba20560c556b4239b69bfef842a20bba99e3239fd3e/packaging_legacy-23.0.post0.tar.gz" + } + ], + "project_name": "packaging-legacy", + "requires_dists": [ + "packaging>=23.0" + ], + "requires_python": null, + "version": "23.0.post0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", + "url": "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", + "url": "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz" + } + ], + "project_name": "passlib", + "requires_dists": [ + "argon2-cffi>=18.2.0; extra == \"argon2\"", + "bcrypt>=3.1.0; extra == \"bcrypt\"", + "cloud-sptheme>=1.10.1; extra == \"build-docs\"", + "cryptography; extra == \"totp\"", + "sphinx>=1.6; extra == \"build-docs\"", + "sphinxcontrib-fulltoc>=1.2.0; extra == \"build-docs\"" + ], + "requires_python": null, + "version": "1.7.4" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "76388ad53a661448d436df28c798063108f70e994ddc749540d733cdbd1b38cf", + "url": "https://files.pythonhosted.org/packages/85/30/cdddd9a88969683a59222a6d61cd6dce923977f2e9f9ffba38e1324149cd/PasteDeploy-3.1.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "9ddbaf152f8095438a9fe81f82c78a6714b92ae8e066bed418b6a7ff6a095a95", + "url": "https://files.pythonhosted.org/packages/e3/97/0c4a613ec96a54d21daa7e089178263915554320402e89b4e319436a63cb/PasteDeploy-3.1.0.tar.gz" + } + ], + "project_name": "pastedeploy", + "requires_dists": [ + "Paste; extra == \"paste\"", + "Paste; extra == \"testing\"", + "Sphinx>=1.7.5; extra == \"docs\"", + "importlib-metadata; python_version < \"3.8\"", + "pylons-sphinx-themes; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"" + ], + "requires_python": ">=3.7", + "version": "3.1.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "42992ab1f4865f1278e2ad740e8ad145683bb4022e03534265528f0c23c0df2d", + "url": "https://files.pythonhosted.org/packages/e7/8b/3f98db1448e3b4d2d142716874a7e02f6101685fdaa0f55a8668e9ffa048/plaster-1.1.2-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "f8befc54bf8c1147c10ab40297ec84c2676fa2d4ea5d6f524d9436a80074ef98", + "url": "https://files.pythonhosted.org/packages/26/93/66df0f87f1442d8afea8531ae8a4a9eca656006a54eac2b4489427e92c10/plaster-1.1.2.tar.gz" + } + ], + "project_name": "plaster", + "requires_dists": [ + "Sphinx; extra == \"docs\"", + "importlib-metadata; python_version < \"3.8\"", + "pylons-sphinx-themes; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"" + ], + "requires_python": ">=3.7", + "version": "1.1.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ad3550cc744648969ed3b810f33c9344f515ee8d8a8cec18e8f2c4a643c2181f", + "url": "https://files.pythonhosted.org/packages/bd/30/2d4cf89035c22a89bf0e34dbc50fdc07c42c9bdc90fd972d495257ad2b6e/plaster_pastedeploy-1.0.1-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "be262e6d2e41a7264875daa2fe2850cbb0615728bcdc92828fdc72736e381412", + "url": "https://files.pythonhosted.org/packages/c7/af/01a22f73ce97c6375c88d7ceaf6f5f4f345e940da93c94f98833d898a449/plaster_pastedeploy-1.0.1.tar.gz" + } + ], + "project_name": "plaster-pastedeploy", + "requires_dists": [ + "PasteDeploy>=2.0", + "plaster>=0.5", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"" + ], + "requires_python": ">=3.7", + "version": "1.0.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617", + "url": "https://files.pythonhosted.org/packages/da/8b/d497999c4017b80678017ddce745cf675489c110681ad3c84a55eddfd3e7/platformdirs-4.3.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "url": "https://files.pythonhosted.org/packages/75/a0/d7cab8409cdc7d39b037c85ac46d92434fb6595432e069251b38e5c8dd0e/platformdirs-4.3.2.tar.gz" + } + ], + "project_name": "platformdirs", + "requires_dists": [ + "appdirs==1.4.4; extra == \"test\"", + "covdefaults>=2.3; extra == \"test\"", + "furo>=2024.8.6; extra == \"docs\"", + "mypy>=1.11.2; extra == \"type\"", + "proselint>=0.14; extra == \"docs\"", + "pytest-cov>=5; extra == \"test\"", + "pytest-mock>=3.14; extra == \"test\"", + "pytest>=8.3.2; extra == \"test\"", + "sphinx-autodoc-typehints>=2.4; extra == \"docs\"", + "sphinx>=8.0.2; extra == \"docs\"" + ], + "requires_python": ">=3.8", + "version": "4.3.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", + "url": "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "url": "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz" + } + ], + "project_name": "pluggy", + "requires_dists": [ + "pre-commit; extra == \"dev\"", + "pytest-benchmark; extra == \"testing\"", + "pytest; extra == \"testing\"", + "tox; extra == \"dev\"" + ], + "requires_python": ">=3.8", + "version": "1.5.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", + "url": "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "url": "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz" + } + ], + "project_name": "py", + "requires_dists": [], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", + "version": "1.11.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", + "url": "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "url": "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz" + } + ], + "project_name": "pycparser", + "requires_dists": [], + "requires_python": ">=3.8", + "version": "2.22" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "2e6585ac55c147f0a51bc00dadf72075b3bdd9a871b332ff9e5e04117ccd76fa", + "url": "https://files.pythonhosted.org/packages/db/41/a2114b8dd2187ae007e022a2baabdc7937cc78211cefc0c01fc5452193af/pyramid-2.0.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "372138a738e4216535cc76dcce6eddd5a1aaca95130f2354fb834264c06f18de", + "url": "https://files.pythonhosted.org/packages/47/c3/5d5736e692fc7ff052577f03136b5edfdf1e2e177eff2f4b91206acae11d/pyramid-2.0.2.tar.gz" + } + ], + "project_name": "pyramid", + "requires_dists": [ + "Sphinx>=3.0.0; extra == \"docs\"", + "coverage; extra == \"testing\"", + "docutils; extra == \"docs\"", + "hupper>=1.5", + "plaster", + "plaster-pastedeploy", + "pylons-sphinx-latesturl; extra == \"docs\"", + "pylons-sphinx-themes>=1.0.8; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest>=5.4.2; extra == \"testing\"", + "repoze.sphinx.autointerface; extra == \"docs\"", + "setuptools", + "sphinx-copybutton; extra == \"docs\"", + "sphinxcontrib-autoprogram; extra == \"docs\"", + "translationstring>=0.4", + "venusian>=1.0", + "webob>=1.8.3", + "webtest>=1.3.1; extra == \"testing\"", + "zope.component>=4.0; extra == \"testing\"", + "zope.deprecation>=3.5.0", + "zope.interface>=3.8.0" + ], + "requires_python": ">=3.6", + "version": "2.0.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", + "url": "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "url": "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz" + } + ], + "project_name": "python-dateutil", + "requires_dists": [ + "six>=1.5" + ], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7", + "version": "2.9.0.post0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "f77bf0e1096ea445beadd35f3479c5cff2aa1efe604a133e67150bc8630a62ea", + "url": "https://files.pythonhosted.org/packages/b0/30/6cc0c95f0b59ad4b3b9163bff7cdcf793cc96fac64cf398ff26271f5cf5e/repoze.lru-0.7-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "0429a75e19380e4ed50c0694e26ac8819b4ea7851ee1fc7583c8572db80aff77", + "url": "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz" + } + ], + "project_name": "repoze-lru", + "requires_dists": [ + "Sphinx; extra == \"docs\"", + "coverage; extra == \"testing\"", + "nose; extra == \"testing\"" + ], + "requires_python": null, + "version": "0.7" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", + "url": "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "url": "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz" + } + ], + "project_name": "requests", + "requires_dists": [ + "PySocks!=1.5.7,>=1.5.6; extra == \"socks\"", + "certifi>=2017.4.17", + "chardet<6,>=3.0.2; extra == \"use-chardet-on-py3\"", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1" + ], + "requires_python": ">=3.8", + "version": "2.32.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", + "url": "https://files.pythonhosted.org/packages/73/67/8ece580cc363331d9a53055130f86b096bf16e38156e33b1d3014fffda6b/ruamel.yaml-0.18.6-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", + "url": "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz" + } + ], + "project_name": "ruamel-yaml", + "requires_dists": [ + "mercurial>5.7; extra == \"docs\"", + "ruamel.yaml.clib>=0.2.7; platform_python_implementation == \"CPython\" and python_version < \"3.13\"", + "ruamel.yaml.jinja2>=0.2; extra == \"jinja2\"", + "ryd; extra == \"docs\"" + ], + "requires_python": ">=3.7", + "version": "0.18.6" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", + "url": "https://files.pythonhosted.org/packages/54/61/c18d378caadac66fa97da5d28758c751730dac7510b6a8b8b096da3fff9a/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", + "url": "https://files.pythonhosted.org/packages/01/b0/4ddef56e9f703d7909febc3a421d709a3482cda25826816ec595b73e3847/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", + "url": "https://files.pythonhosted.org/packages/08/4c/5770b8f318fe404a455141a7a33a5568c27a1f944724e82354c8f3554db2/ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", + "url": "https://files.pythonhosted.org/packages/0d/aa/06db7ca0995b513538402e11280282c615b5ae5f09eb820460d35fb69715/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", + "url": "https://files.pythonhosted.org/packages/18/52/8dc27bbd9ef1d4695975b8dc132c27c431d0186037ad3c731a6dd1c154b9/ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", + "url": "https://files.pythonhosted.org/packages/22/fa/b2a8fd49c92693e9b9b6b11eef4c2a8aedaca2b521ab3e020aa4778efc23/ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", + "url": "https://files.pythonhosted.org/packages/27/38/4cf4d482b84ecdf51efae6635cc5483a83cf5ca9d9c13e205a750e251696/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", + "url": "https://files.pythonhosted.org/packages/30/d3/5fe978cd01a61c12efd24d65fa68c6f28f28c8073a06cf11db3a854390ca/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", + "url": "https://files.pythonhosted.org/packages/46/ab/bab9eb1566cd16f060b54055dd39cf6a34bfa0240c53a7218c43e974295b/ruamel.yaml.clib-0.2.8.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", + "url": "https://files.pythonhosted.org/packages/55/b3/e2531a050758b717c969cbf76c103b75d8a01e11af931b94ba656117fbe9/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", + "url": "https://files.pythonhosted.org/packages/56/a9/e3be88fcebe04016c57207260f2b07c5ecacab86e9f585d10daaa2a4074f/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", + "url": "https://files.pythonhosted.org/packages/57/e4/f572d7e2502854f15291dfa94eebdc687e04db387559f026995c7697af34/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", + "url": "https://files.pythonhosted.org/packages/5a/45/644d839c09c0717c2d7f26b705560ad74b3056085b3bc7f9c2ac2081317b/ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", + "url": "https://files.pythonhosted.org/packages/5c/f0/702e56e12497da7960ed8a6972e5edc50545757c40f1a86a41a5217da7e9/ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", + "url": "https://files.pythonhosted.org/packages/61/ee/4874c9fc96010fce85abefdcbe770650c5324288e988d7a48b527a423815/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", + "url": "https://files.pythonhosted.org/packages/66/98/8de4f22bbfd9135deb3422e96d450c4bc0a57d38c25976119307d2efe0aa/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", + "url": "https://files.pythonhosted.org/packages/7a/a2/eb5e9d088cb9d15c24d956944c09dca0a89108ad6e2e913c099ef36e3f0d/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", + "url": "https://files.pythonhosted.org/packages/7c/b2/389b345a60131593028b0263fddaa580edb4081697a3f3aa1f168f67519f/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", + "url": "https://files.pythonhosted.org/packages/7c/e4/0d19d65e340f93df1c47f323d95fa4b256bb28320290f5fddef90837853a/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", + "url": "https://files.pythonhosted.org/packages/87/a6/efb1add3bac06c25aa4c8ff8c6d3e5e91c539f6600832dd63ff98e2b44cc/ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", + "url": "https://files.pythonhosted.org/packages/88/30/fc45b45d5eaf2ff36cffd215a2f85e9b90ac04e70b97fd4097017abfb567/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", + "url": "https://files.pythonhosted.org/packages/8d/c0/fd7196ca7a1c3867e7068ad1c4ff9230291af3f8adab2f9c2c202ecaf9cb/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", + "url": "https://files.pythonhosted.org/packages/90/8c/6cdb44f548b29eb6328b9e7e175696336bc856de2ff82e5776f860f03822/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", + "url": "https://files.pythonhosted.org/packages/a4/f7/22d6b620ed895a05d40802d8281eff924dc6190f682d933d4efff60db3b5/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", + "url": "https://files.pythonhosted.org/packages/af/dc/133547f90f744a0c827bac5411d84d4e81da640deb3af1459e38c5f3b6a0/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", + "url": "https://files.pythonhosted.org/packages/b1/15/971b385c098e8d0d170893f5ba558452bb7b776a0c90658b8f4dd0e3382b/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", + "url": "https://files.pythonhosted.org/packages/b2/ed/f221e60a4cdc7996aae23643da44b12ef33f457c2a52d590236a6950ac8e/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", + "url": "https://files.pythonhosted.org/packages/c9/ff/f781eb5e2ae011e586d5426e2086a011cf1e0f59704a6cad1387975c5a62/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", + "url": "https://files.pythonhosted.org/packages/ca/01/37ac131614f71b98e9b148b2d7790662dcee92217d2fb4bac1aa377def33/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl" + }, + { + "algorithm": "sha256", + "hash": "fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412", + "url": "https://files.pythonhosted.org/packages/d3/62/c60b034d9a008bbd566eeecf53a5a4c73d191c8de261290db6761802b72d/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", + "url": "https://files.pythonhosted.org/packages/e3/41/f62e67ac651358b8f0d60cfb12ab2daf99b1b69eeaa188d0cec809d943a6/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl" + } + ], + "project_name": "ruamel-yaml-clib", + "requires_dists": [], + "requires_python": ">=3.6", + "version": "0.2.8" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "url": "https://files.pythonhosted.org/packages/cb/9c/9ad11ac06b97e55ada655f8a6bea9d1d3f06e120b178cd578d80e558191d/setuptools-74.1.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6", + "url": "https://files.pythonhosted.org/packages/3e/2c/f0a538a2f91ce633a78daaeb34cbfb93a54bd2132a6de1f6cec028eee6ef/setuptools-74.1.2.tar.gz" + } + ], + "project_name": "setuptools", + "requires_dists": [ + "build[virtualenv]>=1.0.3; extra == \"test\"", + "filelock>=3.4.0; extra == \"test\"", + "furo; extra == \"doc\"", + "importlib-metadata>=6; python_version < \"3.10\" and extra == \"core\"", + "importlib-metadata>=7.0.2; python_version < \"3.10\" and extra == \"type\"", + "importlib-resources>=5.10.2; python_version < \"3.9\" and extra == \"core\"", + "ini2toml[lite]>=0.14; extra == \"test\"", + "jaraco.develop>=7.21; (python_version >= \"3.9\" and sys_platform != \"cygwin\") and extra == \"test\"", + "jaraco.develop>=7.21; sys_platform != \"cygwin\" and extra == \"type\"", + "jaraco.envs>=2.2; extra == \"test\"", + "jaraco.packaging>=9.3; extra == \"doc\"", + "jaraco.path>=3.2.0; extra == \"test\"", + "jaraco.test; extra == \"test\"", + "jaraco.text>=3.7; extra == \"core\"", + "jaraco.tidelift>=1.4; extra == \"doc\"", + "more-itertools>=8.8; extra == \"core\"", + "mypy==1.11.*; extra == \"type\"", + "packaging>=23.2; extra == \"test\"", + "packaging>=24; extra == \"core\"", + "pip>=19.1; extra == \"test\"", + "platformdirs>=2.6.2; extra == \"core\"", + "pygments-github-lexers==0.0.5; extra == \"doc\"", + "pyproject-hooks!=1.1; extra == \"doc\"", + "pyproject-hooks!=1.1; extra == \"test\"", + "pytest!=8.1.*,>=6; extra == \"test\"", + "pytest-checkdocs>=2.4; extra == \"check\"", + "pytest-cov; extra == \"cover\"", + "pytest-enabler>=2.2; extra == \"enabler\"", + "pytest-home>=0.5; extra == \"test\"", + "pytest-mypy; extra == \"type\"", + "pytest-perf; sys_platform != \"cygwin\" and extra == \"test\"", + "pytest-ruff>=0.2.1; sys_platform != \"cygwin\" and extra == \"check\"", + "pytest-subprocess; extra == \"test\"", + "pytest-timeout; extra == \"test\"", + "pytest-xdist>=3; extra == \"test\"", + "rst.linker>=1.9; extra == \"doc\"", + "ruff>=0.5.2; sys_platform != \"cygwin\" and extra == \"check\"", + "sphinx-favicon; extra == \"doc\"", + "sphinx-inline-tabs; extra == \"doc\"", + "sphinx-lint; extra == \"doc\"", + "sphinx-notfound-page<2,>=1; extra == \"doc\"", + "sphinx-reredirects; extra == \"doc\"", + "sphinx>=3.5; extra == \"doc\"", + "sphinxcontrib-towncrier; extra == \"doc\"", + "tomli-w>=1.0.0; extra == \"test\"", + "tomli>=2.0.1; python_version < \"3.11\" and extra == \"core\"", + "towncrier<24.7; extra == \"doc\"", + "virtualenv>=13.0.0; extra == \"test\"", + "wheel>=0.43.0; extra == \"core\"", + "wheel>=0.44.0; extra == \"test\"" + ], + "requires_python": ">=3.8", + "version": "74.1.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "url": "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "url": "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz" + } + ], + "project_name": "six", + "requires_dists": [], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7", + "version": "1.16.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "url": "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", + "url": "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz" + } + ], + "project_name": "sniffio", + "requires_dists": [], + "requires_python": ">=3.7", + "version": "1.3.1" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7", + "url": "https://files.pythonhosted.org/packages/96/7c/a81ef5ef10978dd073a854e0fa93b5d8021d0594b639cc8f6453c3c78a1d/strictyaml-1.7.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407", + "url": "https://files.pythonhosted.org/packages/b3/08/efd28d49162ce89c2ad61a88bd80e11fb77bc9f6c145402589112d38f8af/strictyaml-1.7.3.tar.gz" + } + ], + "project_name": "strictyaml", + "requires_dists": [ + "python-dateutil>=2.6.0" + ], + "requires_python": ">=3.7.0", + "version": "1.7.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "5f4dc4d939573db851c8d840551e1a0fb27b946afe3b95aafc22577eed2d6262", + "url": "https://files.pythonhosted.org/packages/3b/98/36187601a15e3d37e9bfcf0e0e1055532b39d044353b06861c3a519737a9/translationstring-1.4-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "bf947538d76e69ba12ab17283b10355a9ecfbc078e6123443f43f2107f6376f3", + "url": "https://files.pythonhosted.org/packages/14/39/32325add93da9439775d7fe4b4887eb7986dbc1d5675b0431f4531f560e5/translationstring-1.4.tar.gz" + } + ], + "project_name": "translationstring", + "requires_dists": [ + "Sphinx>=1.3.1; extra == \"docs\"", + "docutils; extra == \"docs\"", + "pylons-sphinx-themes; extra == \"docs\"" + ], + "requires_python": null, + "version": "1.4" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "url": "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", + "url": "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz" + } + ], + "project_name": "typing-extensions", + "requires_dists": [], + "requires_python": ">=3.8", + "version": "4.12.2" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "url": "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", + "url": "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz" + } + ], + "project_name": "urllib3", + "requires_dists": [ + "brotli>=1.0.9; platform_python_implementation == \"CPython\" and extra == \"brotli\"", + "brotlicffi>=0.8.0; platform_python_implementation != \"CPython\" and extra == \"brotli\"", + "h2<5,>=4; extra == \"h2\"", + "pysocks!=1.5.7,<2.0,>=1.5.6; extra == \"socks\"", + "zstandard>=0.18.0; extra == \"zstd\"" + ], + "requires_python": ">=3.8", + "version": "2.2.3" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "d1fb1e49927f42573f6c9b7c4fcf61c892af8fdcaa2314daa01d9a560b23488d", + "url": "https://files.pythonhosted.org/packages/2c/d7/36860f68eb977ad685d0f0fda733eca913dbda1bb29bbc5f1c5ba460201a/venusian-3.1.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "eb72cdca6f3139a15dc80f9c95d3c10f8a54a0ba881eeef8e2ec5b42d3ee3a95", + "url": "https://files.pythonhosted.org/packages/f8/39/7c0d9011ec465951aaf71c252effc7c031a04404887422c6f66ba26500e1/venusian-3.1.0.tar.gz" + } + ], + "project_name": "venusian", + "requires_dists": [ + "Sphinx>=4.3.2; extra == \"docs\"", + "coverage; extra == \"testing\"", + "pylons-sphinx-themes; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"", + "repoze.sphinx.autointerface; extra == \"docs\"", + "sphinx-copybutton; extra == \"docs\"" + ], + "requires_python": ">=3.7", + "version": "3.1.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669", + "url": "https://files.pythonhosted.org/packages/5b/a9/485c953a1ac4cb98c28e41fd2c7184072df36bbf99734a51d44d04176878/waitress-3.0.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1", + "url": "https://files.pythonhosted.org/packages/70/34/cb77e5249c433eb177a11ab7425056b32d3b57855377fa1e38b397412859/waitress-3.0.0.tar.gz" + } + ], + "project_name": "waitress", + "requires_dists": [ + "Sphinx>=1.8.1; extra == \"docs\"", + "coverage>=5.0; extra == \"testing\"", + "docutils; extra == \"docs\"", + "pylons-sphinx-themes>=1.0.9; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest; extra == \"testing\"" + ], + "requires_python": ">=3.8.0", + "version": "3.0.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "b60ba63f05c0cf61e086a10c3781a41fcfe30027753a8ae6d819c77592ce83ea", + "url": "https://files.pythonhosted.org/packages/c3/c2/fbc206db211c11ac85f2b440670ff6f43d44d7601f61b95628f56d271c21/WebOb-1.8.8-py2.py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "2abc1555e118fc251e705fc6dc66c7f5353bb9fbfab6d20e22f1c02b4b71bcee", + "url": "https://files.pythonhosted.org/packages/a2/7a/ac5b1ab5636cc3bfc9bab1ed54ff4e8fdeb6367edd911f7337be2248b8ab/webob-1.8.8.tar.gz" + } + ], + "project_name": "webob", + "requires_dists": [ + "Sphinx>=1.7.5; extra == \"docs\"", + "coverage; extra == \"testing\"", + "pylons-sphinx-themes; extra == \"docs\"", + "pytest-cov; extra == \"testing\"", + "pytest-xdist; extra == \"testing\"", + "pytest>=3.1.0; extra == \"testing\"" + ], + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7", + "version": "1.8.8" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "28c2ee983812efb4676d33c7a8c6ade0df191c1c6d652bbbfe6e2eeee067b2d4", + "url": "https://files.pythonhosted.org/packages/c8/7d/24a23d4d6d93744babfb99266eeb97a25ceae58c0f841a872b51c45ee214/zope.deprecation-5.0-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "b7c32d3392036b2145c40b3103e7322db68662ab09b7267afe1532a9d93f640f", + "url": "https://files.pythonhosted.org/packages/ba/de/a47e434ed1804d82f3fd7561aee5c55914c72d87f54cac6b99c15cbe7f89/zope.deprecation-5.0.tar.gz" + } + ], + "project_name": "zope-deprecation", + "requires_dists": [ + "Sphinx; extra == \"docs\"", + "setuptools", + "zope.testrunner; extra == \"test\"" + ], + "requires_python": ">=3.7", + "version": "5.0" + }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e", + "url": "https://files.pythonhosted.org/packages/2b/2c/ad78dd168ea6edfe107512e03270bf48add26c184c8cd9f118bb0cf5cd6f/zope.interface-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4", + "url": "https://files.pythonhosted.org/packages/14/44/d12683e823ced271ae2ca3976f16066634911e02540a9559b09444a4b2d3/zope.interface-7.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996", + "url": "https://files.pythonhosted.org/packages/2b/6f/059521297028f3037f2b19a711be845983151acbdeda1031749a91d07048/zope.interface-7.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493", + "url": "https://files.pythonhosted.org/packages/2c/c2/39964ef5fed7ac1523bab2d1bba244290965da6f720164b603ec07adf0a7/zope.interface-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca", + "url": "https://files.pythonhosted.org/packages/2d/45/a891ee78ba5ef5b5437394f8c2c56c094ed1ab41a80ef7afe50191dce3d2/zope.interface-7.0.3-cp312-cp312-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", + "url": "https://files.pythonhosted.org/packages/3d/ed/0ac414f9373d742d2eb2f436b595ed281031780a405621a4d906096092ea/zope.interface-7.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", + "url": "https://files.pythonhosted.org/packages/44/96/4f9fc7bd7cf77745fa1833aed841a3c187dc241dfd4759fc62d737310e9b/zope.interface-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58", + "url": "https://files.pythonhosted.org/packages/45/58/890cf943c9a7dd82d096a11872c7efb3f0e97e86f71b886018044fb01972/zope.interface-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896", + "url": "https://files.pythonhosted.org/packages/4b/c6/b13d388522826de7d9b69814ec9fc0bcd5674873ef0e16bd5ada53ad52de/zope.interface-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d", + "url": "https://files.pythonhosted.org/packages/5a/a9/9665ba3aa7c6173ea2c3249c85546139119eaf3146f280cea8053e0047b9/zope.interface-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958", + "url": "https://files.pythonhosted.org/packages/6b/ab/9e6f954992e042d0c3d1624b3a9ce62d8ea76383cfba67fbd8b6a5c68ed4/zope.interface-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca", + "url": "https://files.pythonhosted.org/packages/6b/c3/7d18af6971634087a4ddc436e37fc47988c31635cd01948ff668d11c96c4/zope.interface-7.0.3-cp310-cp310-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738", + "url": "https://files.pythonhosted.org/packages/76/e9/c2c8d4df55fc8175c0b850fa4fa5e178bda69018adc8be88cf31896114a2/zope.interface-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a", + "url": "https://files.pythonhosted.org/packages/80/ff/66b5cd662b177de4082cac412a877c7a528ef79a392d90e504f50c041dda/zope.interface-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386", + "url": "https://files.pythonhosted.org/packages/8a/64/2922134a93978b6a8b823f3e784d6af3d5d165fad1f66388b0f89b5695fc/zope.interface-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32", + "url": "https://files.pythonhosted.org/packages/9e/1b/79bcfbdc7d621c410a188f25d78f6e07aff7f608c9589cfba77003769f98/zope.interface-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7", + "url": "https://files.pythonhosted.org/packages/ac/cd/5fadea1cc849632627dcce3416c04902bcefbb6ade381d25c39679181bf2/zope.interface-7.0.3-cp38-cp38-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b", + "url": "https://files.pythonhosted.org/packages/ae/84/594bef7ea0a0bdb50d0bfdbecc6e7504a7bbcfef68d6859e94d03a9239a9/zope.interface-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", + "url": "https://files.pythonhosted.org/packages/be/56/6a57ef0699b857b33a407162f29eade4062596870d335f53e914bb98fd0e/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3", + "url": "https://files.pythonhosted.org/packages/c1/a3/a890f35a62aa25233c95e2af4510aa1df0553be48450bb0840b8d3b2a62c/zope.interface-7.0.3-cp311-cp311-macosx_11_0_arm64.whl" + }, + { + "algorithm": "sha256", + "hash": "799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4", + "url": "https://files.pythonhosted.org/packages/c5/22/461037dfdfcd74d4878ad33147eb7c469850343950e7c84318c9427a6945/zope.interface-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc", + "url": "https://files.pythonhosted.org/packages/c6/91/d3e665df6837629e2eec9cdc8cd1118f1a0e74b586bbec2e6cfc6a1b6c3a/zope.interface-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1", + "url": "https://files.pythonhosted.org/packages/c8/83/7de03efae7fc9a4ec64301d86e29a324f32fe395022e3a5b1a79e376668e/zope.interface-7.0.3.tar.gz" + }, + { + "algorithm": "sha256", + "hash": "d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", + "url": "https://files.pythonhosted.org/packages/ce/bb/51ab7785b2ad3123d5eb85b548f98fe2c0809c6bd452e677b1aca71c3c79/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11", + "url": "https://files.pythonhosted.org/packages/d4/52/41b0ee98dc466dedf3f68df99bcc0cb7de71d12f9a05cd4515eb6908cb38/zope.interface-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + }, + { + "algorithm": "sha256", + "hash": "c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", + "url": "https://files.pythonhosted.org/packages/db/35/c83308ac84552c2242d5d59488dbea9a91c64765e117a71c566ddf896e31/zope.interface-7.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" + }, + { + "algorithm": "sha256", + "hash": "9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b", + "url": "https://files.pythonhosted.org/packages/e9/33/a55311169d3d41b61da7c5b7d528ebb0469263252a71d9510849c0d66201/zope.interface-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", + "url": "https://files.pythonhosted.org/packages/ec/be/6640eb57c4b84a471d691082d0207434d1524e428fba1231c335a4cad446/zope.interface-7.0.3-cp312-cp312-macosx_10_9_x86_64.whl" + }, + { + "algorithm": "sha256", + "hash": "7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3", + "url": "https://files.pythonhosted.org/packages/f4/af/078ec35f73ca3545df6bbde657c377c1161b684669454a7ff7e757bd5f12/zope.interface-7.0.3-cp39-cp39-macosx_11_0_arm64.whl" + } + ], + "project_name": "zope-interface", + "requires_dists": [ + "Sphinx; extra == \"docs\"", + "coverage>=5.0.3; extra == \"test\"", + "coverage>=5.0.3; extra == \"testing\"", + "repoze.sphinx.autointerface; extra == \"docs\"", + "setuptools", + "sphinx-rtd-theme; extra == \"docs\"", + "zope.event; extra == \"test\"", + "zope.event; extra == \"testing\"", + "zope.testing; extra == \"test\"", + "zope.testing; extra == \"testing\"" + ], + "requires_python": ">=3.8", + "version": "7.0.3" + } + ], + "platform_tag": null + } + ], + "only_builds": [], + "only_wheels": [], + "overridden": [], + "path_mappings": {}, + "pex_version": "2.18.1", + "pip_version": "24.2", + "prefer_older_binary": false, + "requirements": [ + "devpi-server" + ], + "requires_python": [ + "<3.14,>=3.8" + ], + "resolver_version": "pip-2020-resolver", + "style": "universal", + "target_systems": [ + "linux", + "mac" + ], + "transitive": true, + "use_pep517": null +} diff --git a/tests/integration/cli/commands/test_venv_create.py b/tests/integration/cli/commands/test_venv_create.py index 3e3e87223..a355a7953 100644 --- a/tests/integration/cli/commands/test_venv_create.py +++ b/tests/integration/cli/commands/test_venv_create.py @@ -3,6 +3,7 @@ from __future__ import absolute_import +import glob import os.path import shutil import subprocess @@ -16,6 +17,7 @@ from pex.cli.commands.venv import InstallLayout from pex.common import open_zip, safe_open from pex.compatibility import commonpath +from pex.dist_metadata import Distribution from pex.interpreter import PythonInterpreter from pex.pep_440 import Version from pex.pep_503 import ProjectName @@ -56,13 +58,33 @@ def cowsay_pex( # type: (...) -> str pex = str(td.join("pex")) - run_pex_command(args=["--lock", lock, "-o", pex]).assert_success() + run_pex_command(args=["--lock", lock, "--include-tools", "-o", pex]).assert_success() assert sorted( [(ProjectName("cowsay"), Version("5.0")), (ProjectName("ansicolors"), Version("1.1.8"))] ) == [(dist.metadata.project_name, dist.metadata.version) for dist in PEX(pex).resolve()] return pex +@pytest.fixture(scope="module") +def pre_resolved_dists( + td, # type: Any + cowsay_pex, # type: str +): + # type: (...) -> str + + dists_dir = str(td.join("dists")) + subprocess.check_call( + args=[cowsay_pex, "repository", "extract", "-f", dists_dir], env=make_env(PEX_TOOLS=1) + ) + assert sorted( + [(ProjectName("cowsay"), Version("5.0")), (ProjectName("ansicolors"), Version("1.1.8"))] + ) == [ + (dist.metadata.project_name, dist.metadata.version) + for dist in map(Distribution.load, glob.glob(os.path.join(dists_dir, "*.whl"))) + ] + return dists_dir + + def test_venv_empty(tmpdir): # type: (Any) -> None @@ -110,12 +132,14 @@ def test_venv( tmpdir, # type: Any lock, # type: str cowsay_pex, # type: str + pre_resolved_dists, # type: str ): # type: (...) -> None assert_venv(tmpdir) assert_venv(tmpdir, "--lock", lock) assert_venv(tmpdir, "--pex-repository", cowsay_pex) + assert_venv(tmpdir, "--pre-resolved-dists", pre_resolved_dists) def test_flat_empty(tmpdir): diff --git a/tests/integration/resolve/test_issue_1907.py b/tests/integration/resolve/test_issue_1907.py new file mode 100644 index 000000000..2d67b7c12 --- /dev/null +++ b/tests/integration/resolve/test_issue_1907.py @@ -0,0 +1,155 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import os.path +import re +import subprocess +import sys +from textwrap import dedent + +import pytest + +from pex.atomic_directory import atomic_directory +from pex.common import safe_open +from pex.pep_503 import ProjectName +from pex.pex import PEX +from pex.typing import TYPE_CHECKING +from testing import PY_VER, data, run_pex_command +from testing.cli import run_pex3 + +if TYPE_CHECKING: + from typing import Any + + +skip_unless_supports_devpi_server_lock = pytest.mark.skipif( + PY_VER < (3, 8) or PY_VER >= (3, 14), reason="The uses a lock that requires Python>=3.8,<3.14" +) + + +@pytest.fixture(scope="session") +def dists(shared_integration_test_tmpdir): + # type: (str) -> str + test_issue_1907_chroot = os.path.join(shared_integration_test_tmpdir, "test_issue_1907_chroot") + with atomic_directory(test_issue_1907_chroot) as chroot: + if not chroot.is_finalized(): + requirements = os.path.join(chroot.work_dir, "requirements.txt") + lock = data.path("locks", "devpi-server.lock.json") + run_pex3("lock", "export", "--format", "pip", lock, "-o", requirements).assert_success() + dists = os.path.join(chroot.work_dir, "dists") + subprocess.check_call( + args=[sys.executable, "-m", "pip", "download", "-r", requirements, "-d", dists] + ) + return os.path.join(test_issue_1907_chroot, "dists") + + +@skip_unless_supports_devpi_server_lock +def test_pre_resolved_dists_nominal( + tmpdir, # type: Any + dists, # type: str +): + # type: (...) -> None + + run_pex_command( + args=[ + "--pre-resolved-dists", + dists, + "devpi-server", + "-c", + "devpi-server", + "--", + "--version", + ] + ).assert_success(expected_output_re=re.escape("6.12.0")) + + +@skip_unless_supports_devpi_server_lock +def test_pre_resolved_dists_subset( + tmpdir, # type: Any + dists, # type: str +): + # type: (...) -> None + + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=["--pre-resolved-dists", dists, "pyramid", "-c", "pdistreport", "-o", pex] + ).assert_success() + + assert not any( + dist + for dist in PEX(pex).resolve() + if ProjectName("devpi-server") == dist.metadata.project_name + ), "The subset should not include devpi-server." + + assert subprocess.check_output(args=[pex]).startswith(b"Pyramid version: 2.0.2") + + +@pytest.fixture +def local_project(tmpdir): + # type: (Any) -> str + + project = os.path.join(str(tmpdir), "project") + with safe_open(os.path.join(project, "app.py"), "w") as fp: + fp.write( + dedent( + """\ + import sys + + from pyramid.scripts.pdistreport import main + + + if __name__ == "__main__": + sys.stdout.write("app: ") + main() + """ + ) + ) + with safe_open(os.path.join(project, "pyproject.toml"), "w") as fp: + fp.write( + dedent( + """\ + [build-system] + requires = ["setuptools"] + backend = "setuptools.build_meta" + + [project] + name = "app" + version = "0.1.0" + dependencies = ["pyramid"] + """ + ) + ) + return project + + +@skip_unless_supports_devpi_server_lock +def test_pre_resolved_dists_local_project_requirement( + tmpdir, # type: Any + dists, # type: str + local_project, # type: str +): + # type: (...) -> None + + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=["--pre-resolved-dists", dists, local_project, "-m", "app", "-o", pex] + ).assert_success() + + assert subprocess.check_output(args=[pex]).startswith(b"app: Pyramid version: 2.0.2") + + +@skip_unless_supports_devpi_server_lock +def test_pre_resolved_dists_project_requirement( + tmpdir, # type: Any + dists, # type: str + local_project, # type: str +): + # type: (...) -> None + + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=["--pre-resolved-dists", dists, "--project", local_project, "-m", "app", "-o", pex] + ).assert_success() + + assert subprocess.check_output(args=[pex]).startswith(b"app: Pyramid version: 2.0.2") diff --git a/tests/integration/test_excludes.py b/tests/integration/test_excludes.py index 057920f9f..80064be19 100644 --- a/tests/integration/test_excludes.py +++ b/tests/integration/test_excludes.py @@ -63,6 +63,7 @@ def requests_certifi_excluded_pex(tmpdir): REQUESTS_LOCK, "--exclude", "certifi", + "--include-tools", "-o", pex, "--pex-root", @@ -154,6 +155,39 @@ def test_exclude( assert_requests_certifi_excluded_pex(pex, certifi_venv) +@skip_unless_compatible_with_requests_lock +def test_pre_resolved_dists_exclude( + tmpdir, # type: Any + certifi_venv, # type: Virtualenv +): + # type: (...) -> None + + pex_repository = requests_certifi_excluded_pex(tmpdir) + dists = os.path.join(str(tmpdir), "dists") + subprocess.check_call( + args=[pex_repository, "repository", "extract", "-f", dists], env=make_env(PEX_TOOLS=1) + ) + + pex_root = PexInfo.from_pex(pex_repository).pex_root + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=[ + "--pre-resolved-dists", + dists, + "--exclude", + "certifi", + "requests", + "-o", + pex, + "--pex-root", + pex_root, + "--runtime-pex-root", + pex_root, + ] + ).assert_success() + assert_requests_certifi_excluded_pex(pex, certifi_venv) + + @skip_unless_compatible_with_requests_lock def test_requirements_pex_exclude( tmpdir, # type: Any diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 5a3367531..3769243e8 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -20,7 +20,7 @@ from pex.cache.dirs import CacheDir from pex.common import is_exe, safe_mkdir, safe_open, safe_rmtree, temporary_dir, touch from pex.compatibility import WINDOWS, commonpath -from pex.dist_metadata import Distribution, Requirement +from pex.dist_metadata import Distribution, Requirement, is_wheel from pex.fetcher import URLFetcher from pex.interpreter import PythonInterpreter from pex.layout import Layout @@ -1360,7 +1360,7 @@ def iter_distributions(pex_root, project_name): for d in dirs: if not d.startswith(project_name): continue - if not d.endswith(".whl"): + if not is_wheel(d): continue wheel_path = os.path.realpath(os.path.join(root, d)) if wheel_path in found: @@ -1843,7 +1843,7 @@ def test_constraint_file_from_url(tmpdir): assert len(dist_paths) == 3 dist_paths.remove("fasteners-0.15-py2.py3-none-any.whl") for dist_path in dist_paths: - assert dist_path.startswith(("six-", "monotonic-")) and dist_path.endswith(".whl") + assert dist_path.startswith(("six-", "monotonic-")) and is_wheel(dist_path) def test_console_script_from_pex_path(tmpdir): diff --git a/tests/integration/test_issue_1179.py b/tests/integration/test_issue_1179.py index 3e68b1f92..66488f71c 100644 --- a/tests/integration/test_issue_1179.py +++ b/tests/integration/test_issue_1179.py @@ -2,9 +2,11 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). import sys +from textwrap import dedent import pytest +from pex.targets import LocalInterpreter from testing import run_pex_command @@ -27,10 +29,17 @@ def test_pip_2020_resolver_engaged(): args=["--resolver-version", "pip-legacy-resolver"] + pex_args, quiet=True ) results.assert_failure() - assert "Failed to resolve compatible distributions:" in results.error assert ( - "1: boto3==1.15.6 requires botocore<1.19.0,>=1.18.6 but 1 incompatible dist was resolved: " - "botocore-1.19.63-py2.py3-none-any.whl" in results.error + dedent( + """\ + Failed to resolve compatible distributions for 1 target: + 1: {target} is not compatible with: + boto3 1.15.6 requires botocore<1.19.0,>=1.18.6 but 1 incompatible dist was resolved: + botocore-1.19.63-py2.py3-none-any.whl + """.format( + target=LocalInterpreter.create().render_description() + ) + ) + in results.error ), results.error - run_pex_command(args=["--resolver-version", "pip-2020-resolver"] + pex_args).assert_success() diff --git a/tests/integration/test_overrides.py b/tests/integration/test_overrides.py index fca13a454..c19d5a3eb 100644 --- a/tests/integration/test_overrides.py +++ b/tests/integration/test_overrides.py @@ -6,6 +6,7 @@ import os.path import re import shutil +import subprocess import sys from collections import defaultdict @@ -17,7 +18,7 @@ from pex.pex import PEX from pex.pex_info import PexInfo from pex.typing import TYPE_CHECKING -from testing import PY39, PY310, PY_VER, data, ensure_python_interpreter, run_pex_command +from testing import PY39, PY310, PY_VER, data, ensure_python_interpreter, make_env, run_pex_command from testing.cli import run_pex3 from testing.lock import extract_lock_option_args, index_lock_artifacts @@ -125,6 +126,28 @@ def test_pex_repository_override(tmpdir): ) +@skip_unless_compatible_with_requests_2_31_0 +def test_pre_resolved_dists_override(tmpdir): + # type: (Any) -> None + + repository_pex = os.path.join(str(tmpdir), "repository.pex") + run_pex_command( + args=["requests==2.31.0", "--override", "idna<2.4", "--include-tools", "-o", repository_pex] + ).assert_success() + dists = os.path.join(str(tmpdir), "dists") + subprocess.check_call( + args=[repository_pex, "repository", "extract", "-f", dists], env=make_env(PEX_TOOLS=1) + ) + + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=["--pre-resolved-dists", dists, "requests", "--override", "idna<2.4", "-o", pex] + ).assert_success() + assert_overrides( + pex, expected_overrides=["idna<2.4"], expected_overridden_dists={"idna": ["2.3"]} + ) + + REQUESTS_LOCK = data.path("locks", "requests.lock.json") diff --git a/tests/resolve/test_resolver_options.py b/tests/resolve/test_resolver_options.py index 261fe78b5..f5f686fd3 100644 --- a/tests/resolve/test_resolver_options.py +++ b/tests/resolve/test_resolver_options.py @@ -1,11 +1,13 @@ # Copyright 2021 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). +import os.path import re from argparse import ArgumentParser import pytest +from pex.common import touch from pex.pex_warnings import PEXWarning from pex.pip.version import PipVersion from pex.resolve import resolver_configuration, resolver_options @@ -13,12 +15,13 @@ BuildConfiguration, PexRepositoryConfiguration, PipConfiguration, + PreResolvedConfiguration, ReposConfiguration, ) from pex.typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List + from typing import Any, List from pex.resolve.resolver_options import ResolverConfiguration @@ -153,6 +156,41 @@ def test_pex_repository(parser): assert "a.pex" == resolver_configuration.pex_repository +def test_pre_resolved_dists( + tmpdir, # type: Any + parser, # type: ArgumentParser +): + # type: (...) -> None + resolver_options.register(parser, include_pre_resolved=True) + + sdist = touch(os.path.join(str(tmpdir), "fake-1.0.tar.gz")) + expected_sdists = [sdist] + + wheel = touch(os.path.join(str(tmpdir), "fake-1.0.py2.py3-none-any.whl")) + expected_wheels = [wheel] + + dists_dir = os.path.join(str(tmpdir), "dists") + touch(os.path.join(dists_dir, "README.md")) + expected_wheels.append(touch(os.path.join(dists_dir, "another-2.0.py3-non-any.whl"))) + expected_sdists.append(touch(os.path.join(dists_dir, "another-2.0.tar.gz"))) + expected_sdists.append(touch(os.path.join(dists_dir, "one_more-3.0.tar.gz"))) + + resolver_configuration = compute_resolver_configuration( + parser, + args=[ + "--pre-resolved-dist", + sdist, + "--pre-resolved-dist", + wheel, + "--pre-resolved-dists", + dists_dir, + ], + ) + assert isinstance(resolver_configuration, PreResolvedConfiguration) + assert sorted(expected_sdists) == sorted(resolver_configuration.sdists) + assert sorted(expected_wheels) == sorted(resolver_configuration.wheels) + + def test_invalid_configuration(parser): # type: (ArgumentParser) -> None resolver_options.register(parser, include_pex_repository=True) diff --git a/tests/test_dist_metadata.py b/tests/test_dist_metadata.py index 4297e8150..bfc1f914c 100644 --- a/tests/test_dist_metadata.py +++ b/tests/test_dist_metadata.py @@ -69,7 +69,7 @@ def downloaded_sdist(requirement): dists = os.listdir(download_dir) assert len(dists) == 1, "Expected 1 dist to be downloaded for {}.".format(requirement) sdist = os.path.join(download_dir, dists[0]) - assert sdist.endswith((".sdist", ".tar.gz", ".zip")) + assert sdist.endswith((".tar.gz", ".zip")) yield sdist diff --git a/tests/test_environment.py b/tests/test_environment.py index cad67934d..5bc776337 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -469,7 +469,7 @@ def test_can_add_handles_optional_build_tag_in_wheel( def test_can_add_handles_invalid_wheel_filename(cpython_38_environment): # type: (PEXEnvironment) -> None dist = create_dist("pep427-invalid.whl") - assert _InvalidWheelName(dist, "pep427-invalid") == cpython_38_environment._can_add(dist) + assert _InvalidWheelName(dist, "pep427-invalid.whl") == cpython_38_environment._can_add(dist) @pytest.fixture diff --git a/tests/test_resolver.py b/tests/test_resolver.py index b068a75b2..8af21d84f 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -12,7 +12,7 @@ import pytest -from pex import targets +from pex import dist_metadata, targets from pex.build_system.pep_517 import build_sdist from pex.common import safe_copy, safe_mkdtemp, temporary_dir from pex.dist_metadata import Distribution, Requirement @@ -523,7 +523,9 @@ def assert_dist( dist = distributions_by_name[project_name] assert version == dist.version - assert is_wheel == (dist.location.endswith(".whl") and zipfile.is_zipfile(dist.location)) + assert is_wheel == ( + dist_metadata.is_wheel(dist.location) and zipfile.is_zipfile(dist.location) + ) assert_dist("project1", "1.0.0", is_wheel=False) assert_dist("project2", "2.0.0", is_wheel=True) From 8cbf5ab185b168cb4b2b34d3897ca19603bb4064 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 12:52:41 -0700 Subject: [PATCH 2/7] Fix Python 2.7 pickle issue. --- pex/resolve/pre_resolved_resolver.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pex/resolve/pre_resolved_resolver.py b/pex/resolve/pre_resolved_resolver.py index 494dd60af..8895c57b3 100644 --- a/pex/resolve/pre_resolved_resolver.py +++ b/pex/resolve/pre_resolved_resolver.py @@ -37,15 +37,12 @@ from pex.util import CacheHelper if TYPE_CHECKING: - from typing import DefaultDict, Dict, Iterable, List + from typing import DefaultDict, Dict, Iterable, List, Tuple def _fingerprint_dist(dist_path): - # type: (str) -> FingerprintedDistribution - return FingerprintedDistribution( - distribution=Distribution.load(dist_path), - fingerprint=CacheHelper.hash(dist_path, hasher=hashlib.sha256), - ) + # type: (str) -> Tuple[str, str] + return dist_path, CacheHelper.hash(dist_path, hasher=hashlib.sha256) def resolve_from_dists( @@ -76,7 +73,11 @@ def resolve_from_dists( ) # type: List[str] with TRACER.timed("Fingerprinting pre-resolved wheels"): fingerprinted_wheels = tuple( - iter_map_parallel( + FingerprintedDistribution( + distribution=Distribution.load(dist_path), fingerprint=fingerprint + ) + for dist_path, fingerprint + in iter_map_parallel( inputs=wheels, function=_fingerprint_dist, max_jobs=pip_configuration.max_jobs, From 08276e339ac9977b5a118dcbdb19cd656fa60bb4 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 12:53:02 -0700 Subject: [PATCH 3/7] Prepare the 2.19.0 release. --- CHANGES.md | 10 ++++++++++ pex/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 060433304..fa488f024 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Release Notes +## 2.19.0 + +This release adds support for a new `--pre-resolved-dists` resolver as +an alternative to the existing Pip resolver, `--lock` resolver and +`--pex-repository` resolvers. Using `--pre-resolved-dists dists/dir/` +behaves much like `--no-pypi --find-links dists/dir/` except that it is +roughly 3x faster. + +* Support `--pre-resolved-dists` resolver. (#2512) + ## 2.18.1 This release fixes `--scie-name-style platform-parent-dir` introduced in diff --git a/pex/version.py b/pex/version.py index 5183b0cb7..95550c4e8 100644 --- a/pex/version.py +++ b/pex/version.py @@ -1,4 +1,4 @@ # Copyright 2015 Pex project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). -__version__ = "2.18.1" +__version__ = "2.19.0" From 797e61b02a0743f62029c9113bcb84148f915591 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 12:57:50 -0700 Subject: [PATCH 4/7] Fix fmt. --- pex/resolve/pre_resolved_resolver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pex/resolve/pre_resolved_resolver.py b/pex/resolve/pre_resolved_resolver.py index 8895c57b3..cf91d923f 100644 --- a/pex/resolve/pre_resolved_resolver.py +++ b/pex/resolve/pre_resolved_resolver.py @@ -76,8 +76,7 @@ def resolve_from_dists( FingerprintedDistribution( distribution=Distribution.load(dist_path), fingerprint=fingerprint ) - for dist_path, fingerprint - in iter_map_parallel( + for dist_path, fingerprint in iter_map_parallel( inputs=wheels, function=_fingerprint_dist, max_jobs=pip_configuration.max_jobs, From f8ed96ac668f3537b0e247fa2bf7c4adb99e1656 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 14:06:53 -0700 Subject: [PATCH 5/7] Re-lock for devpi-server 6.12.1 `Python>=3.10,<3.14`. --- testing/data/locks/devpi-server.lock.json | 391 ++-------------------- 1 file changed, 35 insertions(+), 356 deletions(-) diff --git a/testing/data/locks/devpi-server.lock.json b/testing/data/locks/devpi-server.lock.json index b1cf728cf..377bbad82 100644 --- a/testing/data/locks/devpi-server.lock.json +++ b/testing/data/locks/devpi-server.lock.json @@ -80,34 +80,14 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", - "url": "https://files.pythonhosted.org/packages/ee/0f/a2260a207f21ce2ff4cad00a417c31597f08eafb547e00615bcbf403d8ea/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, - { - "algorithm": "sha256", - "hash": "20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", - "url": "https://files.pythonhosted.org/packages/2e/f1/48888db30b6a4a0c78ab7bc7444058a1135b223b6a2a5f2ac7d6780e7443/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", - "url": "https://files.pythonhosted.org/packages/34/da/d105a3235ae86c1c1a80c1e9c46953e6e53cc8c4c61fb3c5ac8a39bbca48/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", - "url": "https://files.pythonhosted.org/packages/43/f3/20bc53a6e50471dfea16a63dc9b69d2a9ec78fd2b9532cc25f8317e121d9/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + "hash": "e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", + "url": "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl" }, { "algorithm": "sha256", "hash": "8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", "url": "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", - "url": "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl" - }, { "algorithm": "sha256", "hash": "f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", @@ -238,8 +218,8 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", - "url": "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl" + "hash": "72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "url": "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", @@ -301,46 +281,21 @@ "hash": "5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", "url": "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, - { - "algorithm": "sha256", - "hash": "ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", - "url": "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, { "algorithm": "sha256", "hash": "de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", "url": "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", - "url": "https://files.pythonhosted.org/packages/48/08/15bf6b43ae9bd06f6b00ad8a91f5a8fe1069d4c9fab550a866755402724e/cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl" - }, { "algorithm": "sha256", "hash": "de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", "url": "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, - { - "algorithm": "sha256", - "hash": "e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", - "url": "https://files.pythonhosted.org/packages/53/93/7e547ab4105969cc8c93b38a667b82a835dd2cc78f3a7dad6130cfd41e1d/cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, - { - "algorithm": "sha256", - "hash": "31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", - "url": "https://files.pythonhosted.org/packages/56/c4/a308f2c332006206bb511de219efeff090e9d63529ba0a77aae72e82248b/cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - }, { "algorithm": "sha256", "hash": "805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", "url": "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", - "url": "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl" - }, { "algorithm": "sha256", "hash": "3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", @@ -361,11 +316,6 @@ "hash": "30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", "url": "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", - "url": "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" - }, { "algorithm": "sha256", "hash": "706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", @@ -406,11 +356,6 @@ "hash": "045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", "url": "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", - "url": "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl" - }, { "algorithm": "sha256", "hash": "b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", @@ -421,36 +366,11 @@ "hash": "45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", "url": "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", - "url": "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", - "url": "https://files.pythonhosted.org/packages/bb/19/b51af9f4a4faa4a8ac5a0e5d5c2522dcd9703d07fac69da34a36c4d960d3/cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", - "url": "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", - "url": "https://files.pythonhosted.org/packages/c2/5b/f1523dd545f92f7df468e5f653ffa4df30ac222f3c884e51e139878f1cb5/cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, { "algorithm": "sha256", "hash": "c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", "url": "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl" }, - { - "algorithm": "sha256", - "hash": "6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", - "url": "https://files.pythonhosted.org/packages/ca/5b/b63681518265f2f4060d2b60755c1c77ec89e5e045fc3773b72735ddaad5/cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" - }, { "algorithm": "sha256", "hash": "1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", @@ -466,11 +386,6 @@ "hash": "4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", "url": "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", - "url": "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - }, { "algorithm": "sha256", "hash": "733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", @@ -481,16 +396,6 @@ "hash": "edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", "url": "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, - { - "algorithm": "sha256", - "hash": "1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", - "url": "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, - { - "algorithm": "sha256", - "hash": "72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", - "url": "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", @@ -501,11 +406,6 @@ "hash": "1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", "url": "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz" }, - { - "algorithm": "sha256", - "hash": "f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", - "url": "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl" - }, { "algorithm": "sha256", "hash": "610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", @@ -541,16 +441,6 @@ "hash": "4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", "url": "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, - { - "algorithm": "sha256", - "hash": "6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "url": "https://files.pythonhosted.org/packages/13/82/83c188028b6f38d39538442dd127dc794c602ae6d45d66c469f4063a4c30/charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "url": "https://files.pythonhosted.org/packages/16/ea/a9e284aa38cccea06b7056d4cbc7adf37670b1f8a668a312864abf1ff7c6/charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl" - }, { "algorithm": "sha256", "hash": "eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", @@ -561,21 +451,11 @@ "hash": "4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", "url": "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl" }, - { - "algorithm": "sha256", - "hash": "122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "url": "https://files.pythonhosted.org/packages/1f/8d/33c860a7032da5b93382cbe2873261f81467e7b37f4ed91e25fed62fd49b/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, { "algorithm": "sha256", "hash": "b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", "url": "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl" }, - { - "algorithm": "sha256", - "hash": "68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "url": "https://files.pythonhosted.org/packages/2a/9d/a6d15bd1e3e2914af5955c8eb15f4071997e7078419328fee93dfd497eb7/charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl" - }, { "algorithm": "sha256", "hash": "25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", @@ -591,36 +471,16 @@ "hash": "ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", "url": "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "url": "https://files.pythonhosted.org/packages/33/95/ef68482e4a6adf781fae8d183fb48d6f2be8facb414f49c90ba6a5149cd1/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl" - }, { "algorithm": "sha256", "hash": "beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", "url": "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "url": "https://files.pythonhosted.org/packages/34/2a/f392457d45e24a0c9bfc012887ed4f3c54bf5d4d05a5deb970ffec4b7fc0/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, { "algorithm": "sha256", "hash": "55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", "url": "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "url": "https://files.pythonhosted.org/packages/3d/09/d82fe4a34c5f0585f9ea1df090e2a71eb9bb1e469723053e1ee9f57c16f3/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "url": "https://files.pythonhosted.org/packages/3d/85/5b7416b349609d20611a64718bed383b9251b5a601044550f0c8983b8900/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, { "algorithm": "sha256", "hash": "573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", @@ -641,11 +501,6 @@ "hash": "e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", "url": "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "url": "https://files.pythonhosted.org/packages/44/80/b339237b4ce635b4af1c73742459eee5f97201bd92b2371c53e11958392e/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl" - }, { "algorithm": "sha256", "hash": "7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", @@ -656,21 +511,6 @@ "hash": "9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", "url": "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "url": "https://files.pythonhosted.org/packages/51/fd/0ee5b1c2860bb3c60236d05b6e4ac240cf702b67471138571dad91bcfed8/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl" - }, - { - "algorithm": "sha256", - "hash": "34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "url": "https://files.pythonhosted.org/packages/53/cd/aa4b8a4d82eeceb872f83237b2d27e43e637cac9ffaef19a1321c3bafb67/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl" - }, - { - "algorithm": "sha256", - "hash": "ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561", - "url": "https://files.pythonhosted.org/packages/54/7f/cad0b328759630814fcf9d804bfabaf47776816ad4ef2e9938b7e1123d04/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", @@ -686,11 +526,6 @@ "hash": "f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", "url": "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz" }, - { - "algorithm": "sha256", - "hash": "5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "url": "https://files.pythonhosted.org/packages/66/fe/c7d3da40a66a6bf2920cce0f436fa1f62ee28aaf92f412f0bf3b84c8ad6c/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl" - }, { "algorithm": "sha256", "hash": "802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", @@ -706,41 +541,21 @@ "hash": "1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", "url": "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, - { - "algorithm": "sha256", - "hash": "e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "url": "https://files.pythonhosted.org/packages/79/66/8946baa705c588521afe10b2d7967300e49380ded089a62d38537264aece/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - }, { "algorithm": "sha256", "hash": "8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", "url": "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "url": "https://files.pythonhosted.org/packages/81/b2/160893421adfa3c45554fb418e321ed342bb10c0a4549e855b2b2a3699cb/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, { "algorithm": "sha256", "hash": "a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", "url": "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" }, - { - "algorithm": "sha256", - "hash": "b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "url": "https://files.pythonhosted.org/packages/98/69/5d8751b4b670d623aa7a47bef061d69c279e9f922f6705147983aa76c3ce/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, { "algorithm": "sha256", "hash": "8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", "url": "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "url": "https://files.pythonhosted.org/packages/9e/ef/cd47a63d3200b232792e361cd67530173a09eb011813478b1c0fb8aa7226/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl" - }, { "algorithm": "sha256", "hash": "6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", @@ -751,36 +566,11 @@ "hash": "fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", "url": "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "url": "https://files.pythonhosted.org/packages/a8/6f/4ff299b97da2ed6358154b6eb3a2db67da2ae204e53d205aacb18a7e4f34/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl" - }, - { - "algorithm": "sha256", - "hash": "06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "url": "https://files.pythonhosted.org/packages/b3/c1/ebca8e87c714a6a561cfee063f0655f742e54b8ae6e78151f60ba8708b3a/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", "url": "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "url": "https://files.pythonhosted.org/packages/bd/28/7ea29e73eea52c7e15b4b9108d0743fc9e4cc2cdb00d275af1df3d46d360/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl" - }, - { - "algorithm": "sha256", - "hash": "ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "url": "https://files.pythonhosted.org/packages/be/4d/9e370f8281cec2fcc9452c4d1ac513324c32957c5f70c73dd2fa8442a21a/charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" - }, - { - "algorithm": "sha256", - "hash": "d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "url": "https://files.pythonhosted.org/packages/c2/65/52aaf47b3dd616c11a19b1052ce7fa6321250a7a0b975f48d8c366733b9f/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl" - }, { "algorithm": "sha256", "hash": "06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", @@ -791,11 +581,6 @@ "hash": "deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", "url": "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl" }, - { - "algorithm": "sha256", - "hash": "4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "url": "https://files.pythonhosted.org/packages/d1/2f/0d1efd07c74c52b6886c32a3b906fb8afd2fecf448650e73ecb90a5a27f1/charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl" - }, { "algorithm": "sha256", "hash": "0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", @@ -821,11 +606,6 @@ "hash": "6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", "url": "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, - { - "algorithm": "sha256", - "hash": "7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "url": "https://files.pythonhosted.org/packages/e1/9c/60729bf15dc82e3aaf5f71e81686e42e50715a1399770bcde1a9e43d09db/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl" - }, { "algorithm": "sha256", "hash": "f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", @@ -841,20 +621,10 @@ "hash": "90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", "url": "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "url": "https://files.pythonhosted.org/packages/ef/d4/a1d72a8f6aa754fdebe91b848912025d30ab7dced61e9ed8aabbf791ed65/charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl" - }, { "algorithm": "sha256", "hash": "cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", "url": "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl" - }, - { - "algorithm": "sha256", - "hash": "c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "url": "https://files.pythonhosted.org/packages/f7/9d/bcf4a449a438ed6f19790eee543a86a740c77508fbc5ddab210ab3ba3a9a/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl" } ], "project_name": "charset-normalizer", @@ -906,24 +676,25 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "c4a74cad8bb45c0a4f097ec22b0575265121b531747aaa72a1d5a0b471a60dab", - "url": "https://files.pythonhosted.org/packages/72/02/bcf6914dcb29e165ef105ea25f9939fe5c9574083912c6ddd2f5bfce1870/devpi_server-6.12.0-py3-none-any.whl" + "hash": "1bf0859784441a902ec264754569ffb78fcac2250a923ba03948dd64b5f05283", + "url": "https://files.pythonhosted.org/packages/ec/2a/46707c8a8fb0c79c3e64a15ca71827f03e665beaf4ccc509f4f2bbd51c05/devpi_server-6.12.1-py3-none-any.whl" }, { "algorithm": "sha256", - "hash": "31e916f06562d6c29edb926e01a94800666afe6333a84c95c8e11285549212d1", - "url": "https://files.pythonhosted.org/packages/3d/09/9d5c3dfc33d9e6e37e6f393744f0558aaaad4abb45b33f7c789a849eee41/devpi_server-6.12.0.tar.gz" + "hash": "72bb14b545b2dda6f1b279da3885475b0eb3a763152442abcd41ce39219310c5", + "url": "https://files.pythonhosted.org/packages/c1/cd/722e084bc5dfb2a598d1dffb3fb178d6a50a2617bbdf5e4e49e7b369aa78/devpi-server-6.12.1.tar.gz" } ], "project_name": "devpi-server", "requires_dists": [ "argon2-cffi", - "attrs>=21.3.0", + "attrs>=22.2.0", "defusedxml", "devpi-common<5,>3.6.0", "httpx", "itsdangerous>=0.24", "lazy", + "legacy-cgi; python_version >= \"3.13\"", "passlib[argon2]", "platformdirs", "pluggy<2.0,>=0.6.0", @@ -935,7 +706,7 @@ "waitress>=1.0.1" ], "requires_python": ">=3.7", - "version": "6.12.0" + "version": "6.12.1" }, { "artifacts": [ @@ -1119,6 +890,24 @@ "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7", "version": "1.6" }, + { + "artifacts": [ + { + "algorithm": "sha256", + "hash": "8eacc1522d9f76451337a4b5a0abf494158d39250754b0d1bc19a14c6512af9b", + "url": "https://files.pythonhosted.org/packages/9b/da/4cbc703cccc326bac1b4311609e694729134d1e8a2b45c224f7cb2602590/legacy_cgi-2.6.1-py3-none-any.whl" + }, + { + "algorithm": "sha256", + "hash": "f2ada99c747c3d72a473a6aaff6259a61f226b06fe9f3106e495ab83fd8f7a42", + "url": "https://files.pythonhosted.org/packages/48/96/ff14ad0f759f2297a2e61db9c5384d248a6b38c6c1d4452c07d7419676a2/legacy_cgi-2.6.1.tar.gz" + } + ], + "project_name": "legacy-cgi", + "requires_dists": [], + "requires_python": "<4.0,>=3.10", + "version": "2.6.1" + }, { "artifacts": [ { @@ -1477,39 +1266,19 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", - "url": "https://files.pythonhosted.org/packages/54/61/c18d378caadac66fa97da5d28758c751730dac7510b6a8b8b096da3fff9a/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl" + "hash": "aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", + "url": "https://files.pythonhosted.org/packages/27/38/4cf4d482b84ecdf51efae6635cc5483a83cf5ca9d9c13e205a750e251696/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl" }, { "algorithm": "sha256", "hash": "b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", "url": "https://files.pythonhosted.org/packages/01/b0/4ddef56e9f703d7909febc3a421d709a3482cda25826816ec595b73e3847/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", - "url": "https://files.pythonhosted.org/packages/08/4c/5770b8f318fe404a455141a7a33a5568c27a1f944724e82354c8f3554db2/ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl" - }, { "algorithm": "sha256", "hash": "3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", "url": "https://files.pythonhosted.org/packages/0d/aa/06db7ca0995b513538402e11280282c615b5ae5f09eb820460d35fb69715/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", - "url": "https://files.pythonhosted.org/packages/18/52/8dc27bbd9ef1d4695975b8dc132c27c431d0186037ad3c731a6dd1c154b9/ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", - "url": "https://files.pythonhosted.org/packages/22/fa/b2a8fd49c92693e9b9b6b11eef4c2a8aedaca2b521ab3e020aa4778efc23/ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", - "url": "https://files.pythonhosted.org/packages/27/38/4cf4d482b84ecdf51efae6635cc5483a83cf5ca9d9c13e205a750e251696/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", @@ -1525,26 +1294,6 @@ "hash": "1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", "url": "https://files.pythonhosted.org/packages/55/b3/e2531a050758b717c969cbf76c103b75d8a01e11af931b94ba656117fbe9/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", - "url": "https://files.pythonhosted.org/packages/56/a9/e3be88fcebe04016c57207260f2b07c5ecacab86e9f585d10daaa2a4074f/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", - "url": "https://files.pythonhosted.org/packages/57/e4/f572d7e2502854f15291dfa94eebdc687e04db387559f026995c7697af34/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl" - }, - { - "algorithm": "sha256", - "hash": "305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", - "url": "https://files.pythonhosted.org/packages/5a/45/644d839c09c0717c2d7f26b705560ad74b3056085b3bc7f9c2ac2081317b/ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl" - }, - { - "algorithm": "sha256", - "hash": "e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", - "url": "https://files.pythonhosted.org/packages/5c/f0/702e56e12497da7960ed8a6972e5edc50545757c40f1a86a41a5217da7e9/ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl" - }, { "algorithm": "sha256", "hash": "07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", @@ -1560,31 +1309,16 @@ "hash": "ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", "url": "https://files.pythonhosted.org/packages/7a/a2/eb5e9d088cb9d15c24d956944c09dca0a89108ad6e2e913c099ef36e3f0d/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl" }, - { - "algorithm": "sha256", - "hash": "da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", - "url": "https://files.pythonhosted.org/packages/7c/b2/389b345a60131593028b0263fddaa580edb4081697a3f3aa1f168f67519f/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", "url": "https://files.pythonhosted.org/packages/7c/e4/0d19d65e340f93df1c47f323d95fa4b256bb28320290f5fddef90837853a/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", - "url": "https://files.pythonhosted.org/packages/87/a6/efb1add3bac06c25aa4c8ff8c6d3e5e91c539f6600832dd63ff98e2b44cc/ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl" - }, { "algorithm": "sha256", "hash": "840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", "url": "https://files.pythonhosted.org/packages/88/30/fc45b45d5eaf2ff36cffd215a2f85e9b90ac04e70b97fd4097017abfb567/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl" }, - { - "algorithm": "sha256", - "hash": "184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", - "url": "https://files.pythonhosted.org/packages/8d/c0/fd7196ca7a1c3867e7068ad1c4ff9230291af3f8adab2f9c2c202ecaf9cb/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl" - }, { "algorithm": "sha256", "hash": "aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", @@ -1605,11 +1339,6 @@ "hash": "bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", "url": "https://files.pythonhosted.org/packages/b1/15/971b385c098e8d0d170893f5ba558452bb7b776a0c90658b8f4dd0e3382b/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl" }, - { - "algorithm": "sha256", - "hash": "bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", - "url": "https://files.pythonhosted.org/packages/b2/ed/f221e60a4cdc7996aae23643da44b12ef33f457c2a52d590236a6950ac8e/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl" - }, { "algorithm": "sha256", "hash": "46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", @@ -1927,8 +1656,8 @@ "artifacts": [ { "algorithm": "sha256", - "hash": "ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e", - "url": "https://files.pythonhosted.org/packages/2b/2c/ad78dd168ea6edfe107512e03270bf48add26c184c8cd9f118bb0cf5cd6f/zope.interface-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "hash": "21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", + "url": "https://files.pythonhosted.org/packages/be/56/6a57ef0699b857b33a407162f29eade4062596870d335f53e914bb98fd0e/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, { "algorithm": "sha256", @@ -1955,41 +1684,21 @@ "hash": "7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", "url": "https://files.pythonhosted.org/packages/3d/ed/0ac414f9373d742d2eb2f436b595ed281031780a405621a4d906096092ea/zope.interface-7.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", - "url": "https://files.pythonhosted.org/packages/44/96/4f9fc7bd7cf77745fa1833aed841a3c187dc241dfd4759fc62d737310e9b/zope.interface-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, { "algorithm": "sha256", "hash": "6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58", "url": "https://files.pythonhosted.org/packages/45/58/890cf943c9a7dd82d096a11872c7efb3f0e97e86f71b886018044fb01972/zope.interface-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" }, - { - "algorithm": "sha256", - "hash": "382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896", - "url": "https://files.pythonhosted.org/packages/4b/c6/b13d388522826de7d9b69814ec9fc0bcd5674873ef0e16bd5ada53ad52de/zope.interface-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl" - }, { "algorithm": "sha256", "hash": "3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d", "url": "https://files.pythonhosted.org/packages/5a/a9/9665ba3aa7c6173ea2c3249c85546139119eaf3146f280cea8053e0047b9/zope.interface-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, - { - "algorithm": "sha256", - "hash": "1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958", - "url": "https://files.pythonhosted.org/packages/6b/ab/9e6f954992e042d0c3d1624b3a9ce62d8ea76383cfba67fbd8b6a5c68ed4/zope.interface-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, { "algorithm": "sha256", "hash": "db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca", "url": "https://files.pythonhosted.org/packages/6b/c3/7d18af6971634087a4ddc436e37fc47988c31635cd01948ff668d11c96c4/zope.interface-7.0.3-cp310-cp310-macosx_11_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738", - "url": "https://files.pythonhosted.org/packages/76/e9/c2c8d4df55fc8175c0b850fa4fa5e178bda69018adc8be88cf31896114a2/zope.interface-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, { "algorithm": "sha256", "hash": "af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a", @@ -2005,31 +1714,11 @@ "hash": "1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32", "url": "https://files.pythonhosted.org/packages/9e/1b/79bcfbdc7d621c410a188f25d78f6e07aff7f608c9589cfba77003769f98/zope.interface-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" }, - { - "algorithm": "sha256", - "hash": "2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7", - "url": "https://files.pythonhosted.org/packages/ac/cd/5fadea1cc849632627dcce3416c04902bcefbb6ade381d25c39679181bf2/zope.interface-7.0.3-cp38-cp38-macosx_11_0_arm64.whl" - }, - { - "algorithm": "sha256", - "hash": "84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b", - "url": "https://files.pythonhosted.org/packages/ae/84/594bef7ea0a0bdb50d0bfdbecc6e7504a7bbcfef68d6859e94d03a9239a9/zope.interface-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", - "url": "https://files.pythonhosted.org/packages/be/56/6a57ef0699b857b33a407162f29eade4062596870d335f53e914bb98fd0e/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl" - }, { "algorithm": "sha256", "hash": "6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3", "url": "https://files.pythonhosted.org/packages/c1/a3/a890f35a62aa25233c95e2af4510aa1df0553be48450bb0840b8d3b2a62c/zope.interface-7.0.3-cp311-cp311-macosx_11_0_arm64.whl" }, - { - "algorithm": "sha256", - "hash": "799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4", - "url": "https://files.pythonhosted.org/packages/c5/22/461037dfdfcd74d4878ad33147eb7c469850343950e7c84318c9427a6945/zope.interface-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" - }, { "algorithm": "sha256", "hash": "2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc", @@ -2045,11 +1734,6 @@ "hash": "d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", "url": "https://files.pythonhosted.org/packages/ce/bb/51ab7785b2ad3123d5eb85b548f98fe2c0809c6bd452e677b1aca71c3c79/zope.interface-7.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl" }, - { - "algorithm": "sha256", - "hash": "95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11", - "url": "https://files.pythonhosted.org/packages/d4/52/41b0ee98dc466dedf3f68df99bcc0cb7de71d12f9a05cd4515eb6908cb38/zope.interface-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" - }, { "algorithm": "sha256", "hash": "c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", @@ -2064,11 +1748,6 @@ "algorithm": "sha256", "hash": "3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", "url": "https://files.pythonhosted.org/packages/ec/be/6640eb57c4b84a471d691082d0207434d1524e428fba1231c335a4cad446/zope.interface-7.0.3-cp312-cp312-macosx_10_9_x86_64.whl" - }, - { - "algorithm": "sha256", - "hash": "7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3", - "url": "https://files.pythonhosted.org/packages/f4/af/078ec35f73ca3545df6bbde657c377c1161b684669454a7ff7e757bd5f12/zope.interface-7.0.3-cp39-cp39-macosx_11_0_arm64.whl" } ], "project_name": "zope-interface", @@ -2099,10 +1778,10 @@ "pip_version": "24.2", "prefer_older_binary": false, "requirements": [ - "devpi-server" + "devpi-server==6.12.1" ], "requires_python": [ - "<3.14,>=3.8" + "<3.14,>=3.10" ], "resolver_version": "pip-2020-resolver", "style": "universal", From 4f86f5ab5e374ccb06c0dda21e9d1765b2016d22 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 14:25:03 -0700 Subject: [PATCH 6/7] Plumb Pip configuration & test full offline for sources. --- pex/resolve/configured_resolve.py | 1 + tests/integration/resolve/test_issue_1907.py | 49 +++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pex/resolve/configured_resolve.py b/pex/resolve/configured_resolve.py index 412da57af..7a7218835 100644 --- a/pex/resolve/configured_resolve.py +++ b/pex/resolve/configured_resolve.py @@ -101,6 +101,7 @@ def resolve( sdists=resolver_configuration.sdists, wheels=resolver_configuration.wheels, requirement_configuration=requirement_configuration, + pip_configuration=resolver_configuration.pip_configuration, compile=compile_pyc, ignore_errors=ignore_errors, result_type=result_type, diff --git a/tests/integration/resolve/test_issue_1907.py b/tests/integration/resolve/test_issue_1907.py index 2d67b7c12..2f569c12d 100644 --- a/tests/integration/resolve/test_issue_1907.py +++ b/tests/integration/resolve/test_issue_1907.py @@ -3,6 +3,7 @@ from __future__ import absolute_import +import glob import os.path import re import subprocess @@ -15,6 +16,7 @@ from pex.common import safe_open from pex.pep_503 import ProjectName from pex.pex import PEX +from pex.pip.version import PipVersion from pex.typing import TYPE_CHECKING from testing import PY_VER, data, run_pex_command from testing.cli import run_pex3 @@ -24,7 +26,7 @@ skip_unless_supports_devpi_server_lock = pytest.mark.skipif( - PY_VER < (3, 8) or PY_VER >= (3, 14), reason="The uses a lock that requires Python>=3.8,<3.14" + PY_VER < (3, 10) or PY_VER >= (3, 14), reason="The uses a lock that requires Python>=3.10,<3.14" ) @@ -61,7 +63,7 @@ def test_pre_resolved_dists_nominal( "--", "--version", ] - ).assert_success(expected_output_re=re.escape("6.12.0")) + ).assert_success(expected_output_re=re.escape("6.12.1")) @skip_unless_supports_devpi_server_lock @@ -153,3 +155,46 @@ def test_pre_resolved_dists_project_requirement( ).assert_success() assert subprocess.check_output(args=[pex]).startswith(b"app: Pyramid version: 2.0.2") + + +@skip_unless_supports_devpi_server_lock +def test_pre_resolved_dists_offline( + tmpdir, # type: Any + dists, # type: str + local_project, # type: str +): + # type: (...) -> None + + offline = os.path.join(str(tmpdir), "offline") + os.mkdir(offline) + + # In order to go offline and still be able to build sdists, we need both the un-vendored Pip and + # its basic build requirements. + if PipVersion.DEFAULT is not PipVersion.VENDORED: + args = [sys.executable, "-m", "pip", "wheel", "-w", offline] + args.extend(str(req) for req in PipVersion.DEFAULT.requirements) + subprocess.check_call(args) + + for dist in glob.glob(os.path.join(dists, "*")): + dest_dist = os.path.join(offline, os.path.basename(dist)) + if not os.path.exists(dest_dist): + os.symlink(dist, dest_dist) + + pex = os.path.join(str(tmpdir), "pex") + run_pex_command( + args=[ + "--no-pypi", + "--find-links", + offline, + "--pre-resolved-dists", + offline, + "--project", + local_project, + "-m", + "app", + "-o", + pex, + ] + ).assert_success() + + assert subprocess.check_output(args=[pex]).startswith(b"app: Pyramid version: 2.0.2") From 1d726291a9339d343ceb2becb231dc8d08245565 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 13 Sep 2024 15:33:55 -0700 Subject: [PATCH 7/7] Fix 1/2 sort. --- tests/integration/cli/commands/test_venv_create.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/integration/cli/commands/test_venv_create.py b/tests/integration/cli/commands/test_venv_create.py index a355a7953..03686c5df 100644 --- a/tests/integration/cli/commands/test_venv_create.py +++ b/tests/integration/cli/commands/test_venv_create.py @@ -78,10 +78,12 @@ def pre_resolved_dists( ) assert sorted( [(ProjectName("cowsay"), Version("5.0")), (ProjectName("ansicolors"), Version("1.1.8"))] - ) == [ - (dist.metadata.project_name, dist.metadata.version) - for dist in map(Distribution.load, glob.glob(os.path.join(dists_dir, "*.whl"))) - ] + ) == sorted( + [ + (dist.metadata.project_name, dist.metadata.version) + for dist in map(Distribution.load, glob.glob(os.path.join(dists_dir, "*.whl"))) + ] + ) return dists_dir