diff --git a/pex/bin/pex.py b/pex/bin/pex.py index 0437dc869..32eb0a857 100755 --- a/pex/bin/pex.py +++ b/pex/bin/pex.py @@ -172,12 +172,12 @@ def configure_clp_pex_resolution(parser, builder): group.add_option( '--pre', '--no-pre', dest='allow_prereleases', - default=False, + default=None, action='callback', callback=process_prereleases, callback_args=(builder,), help='Whether to include pre-release and development versions of requirements; ' - 'Default: only stable versions are used') + 'Default: only stable versions are used, unless explicitly requested') group.add_option( '--disable-cache', diff --git a/pex/iterator.py b/pex/iterator.py index 1828178ee..51769274a 100644 --- a/pex/iterator.py +++ b/pex/iterator.py @@ -22,7 +22,7 @@ def iter(self, req): class Iterator(IteratorInterface): """A requirement iterator, the glue between fetchers, crawlers and requirements.""" - def __init__(self, fetchers=None, crawler=None, follow_links=False, allow_prereleases=False): + def __init__(self, fetchers=None, crawler=None, follow_links=False, allow_prereleases=None): self._crawler = crawler or Crawler() self._fetchers = fetchers if fetchers is not None else [PyPIFetcher()] self.__follow_links = follow_links diff --git a/pex/package.py b/pex/package.py index 86968fe27..a38f8d334 100644 --- a/pex/package.py +++ b/pex/package.py @@ -67,18 +67,20 @@ def raw_version(self): def version(self): return parse_version(self.raw_version) - def satisfies(self, requirement, allow_prereleases=False): + def satisfies(self, requirement, allow_prereleases=None): """Determine whether this package matches the requirement. :param requirement: The requirement to compare this Package against :type requirement: string or :class:`pkg_resources.Requirement` - :param bool allow_prereleases: Whether to allow prereleases to satisfy the `requirement`. + :param Optional[bool] allow_prereleases: Whether to allow prereleases to satisfy + the `requirement`. :returns: True if the package matches the requirement, otherwise False """ requirement = maybe_requirement(requirement) link_name = safe_name(self.name).lower() if link_name != requirement.key: return False + # NB: If we upgrade to setuptools>=34 the SpecifierSet used here (requirement.specifier) will # come from a non-vendored `packaging` package and pex's bootstrap code in `PEXBuilder` will # need an update. diff --git a/pex/requirements.py b/pex/requirements.py index 17810f230..e53aede81 100644 --- a/pex/requirements.py +++ b/pex/requirements.py @@ -56,6 +56,8 @@ def requirements_from_lines(lines, builder=None, relpath=None): builder.allow_unverified(_get_parameter(line)) elif line.startswith('--pre'): builder.allow_prereleases(True) + elif line.startswith('--no-pre'): + builder.allow_prereleases(False) elif line.startswith('--no-index'): builder.clear_indices() elif line.startswith('--no-use-wheel'): diff --git a/pex/resolver.py b/pex/resolver.py index adc11382a..41f0e0777 100644 --- a/pex/resolver.py +++ b/pex/resolver.py @@ -283,7 +283,7 @@ def resolve( precedence=None, cache=None, cache_ttl=None, - allow_prereleases=False): + allow_prereleases=None): """Produce all distributions needed to (recursively) meet `requirements` @@ -313,7 +313,7 @@ def resolve( resolving inexact requirements will always result in making network calls through the ``context``. :keyword allow_prereleases: (optional) Include pre-release and development versions. If - unspecified only stable versions will be resolved. + unspecified only stable versions will be resolved, unless explicitly included. :returns: List of :class:`pkg_resources.Distribution` instances meeting ``requirements``. :raises Unsatisfiable: If ``requirements`` is not transitively satisfiable. :raises Untranslateable: If no compatible distributions could be acquired for diff --git a/pex/resolver_options.py b/pex/resolver_options.py index c41b56a7e..1d2d29f2f 100644 --- a/pex/resolver_options.py +++ b/pex/resolver_options.py @@ -43,7 +43,7 @@ def __init__(self, allow_all_external=False, allow_external=None, allow_unverified=None, - allow_prereleases=False, + allow_prereleases=None, precedence=None, context=None): self._fetchers = fetchers if fetchers is not None else [PyPIFetcher()] @@ -137,7 +137,7 @@ def __init__(self, fetchers=None, allow_external=False, allow_unverified=False, - allow_prereleases=False, + allow_prereleases=None, precedence=None, context=None): self._fetchers = fetchers if fetchers is not None else [PyPIFetcher()] diff --git a/tests/test_package.py b/tests/test_package.py index 4975abfaa..7ab22f787 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -128,3 +128,22 @@ def wheel_package(version): assert not prerelease_package.satisfies(requirement, allow_prereleases=False) assert prerelease_package.satisfies(requirement, allow_prereleases=True) + + +def test_explicit_prereleases(): + def source_package(version): + return SourcePackage('setuptools-%s.tar.gz' % version) + + def egg_package(version): + return EggPackage('setuptools-%s-py2.7.egg' % version) + + def wheel_package(version): + return WheelPackage('file:///tmp/setuptools-%s-py2.py3-none-any.whl' % version) + + requirement = 'setuptools==7.0b1' + + for package in (egg_package, source_package, egg_package, wheel_package): + prerelease_package = package('7.0b1') + + # satisfies should not exclude prereleases if explicitly requested + assert prerelease_package.satisfies(requirement)