def is_old(latest, specifiers): """ :param latest Version: :param required SpecifierSet: """ if not isinstance(latest, Version): raise TypeError('latest is not of type Version') if not isinstance(specifiers, SpecifierSet): raise TypeError('specifiers is not of type SpecifierSet') old = False for spec in specifiers: if spec.operator in ['>', '>=']: continue latest_op = '<' if spec.operator in ['<']: latest_op = '<=' latest_spec = Specifier('{}{}'.format(latest_op, str(latest))) if list(latest_spec.filter([spec.version])): old = True return old
def get_specset(marker_list): # type: (List) -> Optional[SpecifierSet] specset = set() _last_str = "and" for marker_parts in marker_list: if isinstance(marker_parts, tuple): variable, op, value = marker_parts if variable.value != "python_version": continue if op.value == "in": values = [v.strip() for v in value.value.split(",")] specset.update(Specifier("=={0}".format(v)) for v in values) elif op.value == "not in": values = [v.strip() for v in value.value.split(",")] bad_versions = ["3.0", "3.1", "3.2", "3.3"] if len(values) >= 2 and any(v in values for v in bad_versions): values = bad_versions specset.update( Specifier("!={0}".format(v.strip())) for v in sorted(bad_versions) ) else: specset.add(Specifier("{0}{1}".format(op.value, value.value))) elif isinstance(marker_parts, list): parts = get_specset(marker_parts) if parts: specset.update(parts) elif isinstance(marker_parts, str): _last_str = marker_parts specifiers = SpecifierSet() specifiers._specs = frozenset(specset) return specifiers
def _format_pyspec(specifier): # type: (Union[STRING_TYPE, Specifier]) -> Specifier if isinstance(specifier, str): if not any(op in specifier for op in Specifier._operators.keys()): specifier = "=={0}".format(specifier) specifier = Specifier(specifier) version = getattr(specifier, "version", specifier).rstrip() if version and version.endswith("*"): if version.endswith(".*"): version = version.rstrip(".*") else: version = version.rstrip("*") specifier = Specifier("{0}{1}".format(specifier.operator, version)) try: op = REPLACE_RANGES[specifier.operator] except KeyError: return specifier curr_tuple = _tuplize_version(version) try: next_tuple = (curr_tuple[0], curr_tuple[1] + 1) except IndexError: next_tuple = (curr_tuple[0], 1) if not next_tuple[1] <= MAX_VERSIONS[next_tuple[0]]: if specifier.operator == "<" and curr_tuple[1] <= MAX_VERSIONS[next_tuple[0]]: op = "<=" next_tuple = (next_tuple[0], curr_tuple[1]) else: return specifier specifier = Specifier("{0}{1}".format(op, _format_version(next_tuple))) return specifier
def matches_version(self, other): """ returns true of self.version fits within the specification given by other.version """ if not isinstance(other, Package): other = Package(other) if other.version is None or other.version == '*': return True elif other.delimiter == '=': other_delim = '==' else: other_delim = other.delimiter if (other_delim in ('==', '!=') and (other.minor_version is None or other.patch_version is None) and not other.version.endswith('.*')): other_version = f'{other.version}.*' elif other_delim not in ('==', '!=') and other.version.endswith('.*'): other_version = other.version.split('.*')[0] else: other_version = other.version other_spec = Specifier(f'{other_delim}{other_version}') return other_spec.contains(self.version)
def test_specifier_filter(self, specifier, prereleases, input, expected): spec = Specifier(specifier) kwargs = ({ "prereleases": prereleases } if prereleases is not None else {}) assert list(spec.filter(input, **kwargs)) == expected
def test_specifier_filter(self, specifier, prereleases, input, expected): spec = Specifier(specifier) kwargs = ( {"prereleases": prereleases} if prereleases is not None else {} ) assert list(spec.filter(input, **kwargs)) == expected
def test_specifiers_identity(self, version, spec, expected): spec = Specifier(spec) if expected: # Identity comparisons only support the plain string form assert spec.contains(version) else: # Identity comparisons only support the plain string form assert not spec.contains(version)
def _versions_match(required, installed): if required is None: return True try: req = Specifier(required) except InvalidSpecifier: req = LegacySpecifier(required) contains = req.contains(installed[2:]) return contains
def test_specifiers_prereleases(self, specifier, version, expected): spec = Specifier(specifier) if expected: assert version in spec spec.prereleases = False assert version not in spec else: assert version not in spec spec.prereleases = True assert version in spec
def test_specifiers(self, version, spec, expected): spec = Specifier(spec, prereleases=True) if expected: # Test that the plain string form works assert spec.contains(version) # Test that the version instance form works assert spec.contains(Version(version)) else: # Test that the plain string form works assert not spec.contains(version) # Test that the version instance form works assert not spec.contains(Version(version))
def _get_specifiers_from_markers(marker_item): """ Given a marker item, get specifiers from the version marker :param :class:`~packaging.markers.Marker` marker_sequence: A marker describing a version constraint :return: A set of specifiers corresponding to the marker constraint :rtype: Set[Specifier] """ specifiers = set() if isinstance(marker_item, tuple): variable, op, value = marker_item if variable.value != "python_version": return specifiers if op.value == "in": specifiers.update(_split_specifierset_str(value.value, prefix="==")) elif op.value == "not in": specifiers.update(_split_specifierset_str(value.value, prefix="!=")) else: specifiers.add(Specifier("{0}{1}".format(op.value, value.value))) elif isinstance(marker_item, list): parts = get_specset(marker_item) if parts: specifiers.update(parts) return specifiers
def update_config(self, config: Dict[str, str]) -> None: identifier = config["identifier"] version = Version(config["version"]) spec = Specifier(f"=={version.major}.{version.minor}.*") log.info(f"Reading in '{identifier}' -> {spec} @ {version}") orig_config = copy.copy(config) config_update: Optional[AnyConfig] # We need to use ** in update due to MyPy (probably a bug) if "macosx_x86_64" in identifier: if identifier.startswith("pp"): config_update = self.macos_pypy.update_version_macos(spec) else: config_update = self.macos_9.update_version_macos( spec) or self.macos_6.update_version_macos(spec) assert config_update is not None, f"MacOS {spec} not found!" config.update(**config_update) elif "win32" in identifier: if identifier.startswith("pp"): config.update(**self.windows_pypy.update_version_windows(spec)) else: config_update = self.windows_32.update_version_windows(spec) if config_update: config.update(**config_update) elif "win_amd64" in identifier: config_update = self.windows_64.update_version_windows(spec) if config_update: config.update(**config_update) if config != orig_config: log.info(f" Updated {orig_config} to {config}")
def update_version_macos(self, spec: Specifier) -> Optional[ConfigMacOS]: sorted_versions = sorted(v for v in self.versions_dict if spec.contains(v)) for version in reversed(sorted_versions): # Find the first patch version that contains the requested file uri = self.versions_dict[version] response = requests.get( f"https://www.python.org/api/v2/downloads/release_file/?release={uri}" ) response.raise_for_status() file_info = response.json() urls = [ rf["url"] for rf in file_info if self.file_ident in rf["url"] ] if urls: return ConfigMacOS( identifier= f"cp{version.major}{version.minor}-{self.plat_arch}", version=f"{version.major}.{version.minor}", url=urls[0], ) return None
def update_version_macos(self, spec: Specifier) -> ConfigMacOS: if self.arch != "64": raise RuntimeError("Other archs not supported yet on macOS") releases = [ r for r in self.releases if spec.contains(r["python_version"]) ] releases = sorted(releases, key=lambda r: r["pypy_version"]) if not releases: raise RuntimeError(f"PyPy macOS {self.arch} not found for {spec}!") release = releases[-1] version = release["python_version"] identifier = f"pp{version.major}{version.minor}-macosx_x86_64" (url, ) = [ rf["download_url"] for rf in release["files"] if "" in rf["platform"] == "darwin" and rf["arch"] == "x64" ] return ConfigMacOS( identifier=identifier, version=f"{version.major}.{version.minor}", url=url, )
def update_version_windows(self, spec: Specifier) -> ConfigWinCP: if self.arch != "32": raise RuntimeError("64 bit releases not supported yet on Windows") releases = [ r for r in self.releases if spec.contains(r["python_version"]) ] releases = sorted(releases, key=lambda r: r["pypy_version"]) if not releases: raise RuntimeError( f"PyPy Win {self.arch} not found for {spec}! {self.releases}") release = releases[-1] version = release["python_version"] identifier = f"pp{version.major}{version.minor}-win32" (url, ) = [ rf["download_url"] for rf in release["files"] if "" in rf["platform"] == "win32" ] return ConfigWinPP( identifier=identifier, version=f"{version.major}.{version.minor}", arch="32", url=url, )
def update_req(req): """Updates a given req object with the latest version.""" if not req.name: return req, None info = get_package_info(req.name) if info['info'].get('_pypi_hidden'): print('{} is hidden on PyPI and will not be updated.'.format(req)) return req, None if _is_pinned(req) and _is_version_range(req): print('{} is pinned to a range and will not be updated.'.format(req)) return req, None newest_version = _get_newest_version(info) current_spec = next(iter(req.specifier)) if req.specifier else None current_version = current_spec.version if current_spec else None new_spec = Specifier(u'=={}'.format(newest_version)) if not current_spec or current_spec._spec != new_spec._spec: req.specifier = new_spec update_info = (req.name, current_version, newest_version) return req, update_info return req, None
def update_version_windows(self, spec: Specifier) -> ConfigWinCP: releases = [ r for r in self.releases if spec.contains(r["python_version"]) ] releases = sorted( releases, key=lambda r: r["pypy_version"]) # type: ignore[no-any-return] releases = [r for r in releases if self.get_arch_file(r)] if not releases: raise RuntimeError( f"PyPy Win {self.arch} not found for {spec}! {self.releases}") version_arch = "win32" if self.arch == "32" else "win_amd64" release = releases[-1] version = release["python_version"] identifier = f"pp{version.major}{version.minor}-{version_arch}" url = self.get_arch_file(release) return ConfigWinPP( identifier=identifier, version=f"{version.major}.{version.minor}", arch=self.arch, url=url, )
def update_version_macos(self, identifier: str, version: Version, spec: Specifier) -> ConfigMacOS | None: sorted_versions = sorted(v for v in self.versions_dict if spec.contains(v)) if version <= Version("3.8.9999"): file_ident = "macosx10.9.pkg" else: file_ident = "macos11.pkg" for new_version in reversed(sorted_versions): # Find the first patch version that contains the requested file uri = self.versions_dict[new_version] response = requests.get( f"https://www.python.org/api/v2/downloads/release_file/?release={uri}" ) response.raise_for_status() file_info = response.json() urls = [rf["url"] for rf in file_info if file_ident in rf["url"]] if urls: return ConfigMacOS( identifier=identifier, version=f"{new_version.major}.{new_version.minor}", url=urls[0], ) return None
def update_config(self, config: dict[str, str]) -> None: identifier = config["identifier"] version = Version(config["version"]) spec = Specifier(f"=={version.major}.{version.minor}.*") log.info(f"Reading in '{identifier}' -> {spec} @ {version}") orig_config = copy.copy(config) config_update: AnyConfig | None = None # We need to use ** in update due to MyPy (probably a bug) if "macos" in identifier: if identifier.startswith("cp"): config_update = self.macos_cpython.update_version_macos( identifier, version, spec) elif identifier.startswith("pp"): config_update = self.macos_pypy.update_version_macos(spec) elif "win32" in identifier: if identifier.startswith("cp"): config_update = self.windows_32.update_version_windows(spec) elif "win_amd64" in identifier: if identifier.startswith("cp"): config_update = self.windows_64.update_version_windows(spec) elif identifier.startswith("pp"): config_update = self.windows_pypy_64.update_version_windows( spec) elif "win_arm64" in identifier: if identifier.startswith("cp"): config_update = self.windows_arm64.update_version_windows(spec) assert config_update is not None, f"{identifier} not found!" config.update(**config_update) if config != orig_config: log.info(f" Updated {orig_config} to {config}")
def __init__(self, specifiers=''): specifiers = [s.strip() for s in specifiers.split(',') if s.strip()] parsed = set() for specifier in specifiers: parsed.add(Specifier(specifier)) self._specs = frozenset(parsed) self._prereleases = None
def pyspec_from_markers(marker): if marker._markers[0][0] != 'python_version': return op = marker._markers[0][1].value version = marker._markers[0][2].value specset = set() if op == "in": specset.update( Specifier("=={0}".format(v.strip())) for v in version.split(",")) elif op == "not in": specset.update( Specifier("!={0}".format(v.strip())) for v in version.split(",")) else: specset.add(Specifier("".join([op, version]))) if specset: return specset return None
def test_specifiers_normalized(self, version): if "+" not in version: ops = ["~=", "==", "!=", "<=", ">=", "<", ">"] else: ops = ["==", "!="] for op in ops: Specifier(op + version)
def pre_run(self) -> None: """Parse and initialize specifier used.""" if self.configuration["package_name"] is None: raise SieveError(f"Package name for {self.__class__.__name__!r} is not set") if self.configuration["version_specifier"] is None: raise SieveError(f"Package version specifier for {self.__class__.__name__!r} is not set") self._specifier = Specifier(self.configuration["version_specifier"])
def test_specifiers_prereleases(self, specifier, version, expected): spec = Specifier(specifier) if expected: assert spec.contains(version) spec.prereleases = False assert not spec.contains(version) else: assert not spec.contains(version) spec.prereleases = True assert spec.contains(version)
def _format_pyspec(specifier): if isinstance(specifier, str): if not any(op in specifier for op in Specifier._operators.keys()): specifier = "=={0}".format(specifier) specifier = Specifier(specifier) if specifier.operator == "==" and specifier.version.endswith(".*"): specifier = Specifier("=={0}".format(specifier.version[:-2])) try: op = REPLACE_RANGES[specifier.operator] except KeyError: return specifier version = specifier.version.replace(".*", "") curr_tuple = _tuplize_version(version) try: next_tuple = (curr_tuple[0], curr_tuple[1] + 1) except IndexError: next_tuple = (curr_tuple[0], 1) specifier = Specifier("{0}{1}".format(op, _format_version(next_tuple))) return specifier
def verify_mininum(mininum_package, required_package_name, required_mininum_version, operator='==', required_extra=None): assert mininum_package.name == required_package_name assert mininum_package.specifier == Specifier(operator + required_mininum_version) if required_extra: assert mininum_package.extras == {required_extra} else: assert mininum_package.extras == set() extra_chars = ['[', ']'] not any([x in mininum_package.name for x in extra_chars]) assert not any([x in required_package_name for x in extra_chars])
def _handle_compatibility_operator( all_specifiers: List[Specifier], operator_to_specifiers: Dict[str, Set[Specifier]], specifier: Specifier, ) -> None: """ Handle a specifier with operator '~='. Split specifier of the form "~=<major.minor>" in two specifiers: - >= <major.minor> - < <major+1> Also handle micro-numbers, i.e. "~=<major.minor.micro>" (see last examples of https://www.python.org/dev/peps/pep-0440/#compatible-release) :param all_specifiers: the list of all specifiers (to be populated). :param operator_to_specifiers: a mapping from operator to specifiers (to be populated). :param specifier: the specifier to process. :return: None """ spec_version = Version(specifier.version) base_version = spec_version.base_version parts = base_version.split(".") index_to_update = -2 if (spec_version.is_prerelease or spec_version.is_devrelease or spec_version.is_postrelease): # if it is a pre-release, ignore the suffix. index_to_update += 1 parts = parts[:-1] # bump second-to-last part parts[index_to_update] = str(int(parts[index_to_update]) + 1) upper_version = Version(".".join(parts)) spec_1 = Specifier(">=" + str(spec_version)) spec_2 = Specifier("<" + str(upper_version)) all_specifiers.extend([spec_1, spec_2]) operator_to_specifiers[spec_1.operator].add(spec_1) operator_to_specifiers[spec_2.operator].add(spec_2)
def update_version_windows(self, spec: Specifier) -> ConfigWinCP | None: versions = sorted(v for v in self.versions if spec.contains(v)) if not all(v.is_prerelease for v in versions): versions = [v for v in versions if not v.is_prerelease] log.debug(f"Windows {self.arch} {spec} has {', '.join(str(v) for v in versions)}") if not versions: return None version = versions[-1] identifier = f"cp{version.major}{version.minor}-{self.arch}" return ConfigWinCP( identifier=identifier, version=str(version), arch=self.arch_str, )
class VersionConstraintSieve(Sieve): """Filter out packages based on version constraints if they occur in the stack.""" CONFIGURATION_DEFAULT = {"package_name": None, "version_specifier": None} CONFIGURATION_SCHEMA = Schema({ Required("package_name"): str, Required("version_specifier"): str }) _specifier = attr.ib(type=Optional[Specifier], default=None, init=False) @classmethod def should_include( cls, builder_context: "PipelineBuilderContext" ) -> Generator[Dict[str, Any], None, None]: """Include this sieve only if user explicitly asks for it.""" yield from () return None def pre_run(self) -> None: """Parse and initialize specifier used.""" self._specifier = Specifier(self.configuration["version_specifier"]) super().pre_run() def run( self, package_versions: Generator[PackageVersion, None, None] ) -> Generator[PackageVersion, None, None]: """Filter out packages based on build time/installation issues..""" if self._specifier is None: return for package_version in package_versions: if package_version.name == self.configuration[ "package_name"] and not self._specifier.contains( package_version.locked_version): _LOGGER.debug( "Removing package %r based on configured version specifier %r", self.configuration["package_name"], self.configuration["version_specifier"], ) continue yield package_version
def _split_specifierset_str(specset_str, prefix="=="): # type: (str, str) -> Set[Specifier] """Take a specifierset string and split it into a list to join for specifier sets. :param str specset_str: A string containing python versions, often comma separated :param str prefix: A prefix to use when generating the specifier set :return: A list of :class:`Specifier` instances generated with the provided prefix :rtype: Set[Specifier] """ specifiers = set() if "," not in specset_str and " " in specset_str: values = [v.strip() for v in specset_str.split()] else: values = [v.strip() for v in specset_str.split(",")] if prefix == "!=" and any(v in values for v in DEPRECATED_VERSIONS): values += DEPRECATED_VERSIONS[:] for value in sorted(values): specifiers.add(Specifier("{0}{1}".format(prefix, value))) return specifiers
def update_version_windows(self, spec: Specifier) -> ConfigWinCP | None: # Specifier.filter selects all non pre-releases that match the spec, # unless there are only pre-releases, then it selects pre-releases # instead (like pip) unsorted_versions = spec.filter(self.version_dict) versions = sorted(unsorted_versions, reverse=True) log.debug( f"Windows {self.arch} {spec} has {', '.join(str(v) for v in versions)}" ) if not versions: return None version = versions[0] identifier = f"cp{version.major}{version.minor}-{self.arch}" return ConfigWinCP( identifier=identifier, version=self.version_dict[version], arch=self.arch_str, )
def test_empty_specifier(self, version): spec = Specifier(prereleases=True) assert spec.contains(version)
def test_specifier_prereleases_explicit(self): spec = Specifier() assert not spec.prereleases assert not spec.contains("1.0.dev1") spec.prereleases = True assert spec.prereleases assert spec.contains("1.0.dev1") spec = Specifier(prereleases=True) assert spec.prereleases assert spec.contains("1.0.dev1") spec.prereleases = False assert not spec.prereleases assert not spec.contains("1.0.dev1") spec = Specifier(prereleases=True) assert spec.prereleases assert spec.contains("1.0.dev1") spec.prereleases = None assert not spec.prereleases assert not spec.contains("1.0.dev1")