Пример #1
0
    def _analyze_specifiers(self) -> None:
        lower_bound, upper_bound = Version.MIN, Version.MAX
        excludes: Set[Version] = set()
        for spec in self:
            op, version = _normalize_op_specifier(spec.operator, spec.version)

            if op in ("==", "==="):
                lower_bound = version
                upper_bound = version.bump()
                break
            if op == "!=":
                excludes.add(version)
            elif op[0] == ">":
                lower_bound = max(lower_bound,
                                  version if op == ">=" else version.bump())
            elif op[0] == "<":
                upper_bound = min(upper_bound,
                                  version.bump() if op == "<=" else version)
            elif op == "~=":
                new_lower = version.complete()
                new_upper = version.bump(-2)
                if new_upper < upper_bound:
                    upper_bound = new_upper
                if new_lower > lower_bound:
                    lower_bound = new_lower
            else:
                raise InvalidPyVersion(
                    f"Unsupported version specifier: {op}{version}")
        self._rearrange(lower_bound, upper_bound, excludes)
Пример #2
0
def parse_version_tuple(version: str) -> Tuple[Union[int, str], ...]:
    version = re.sub(r"(?<!\.)\*", ".*", version)
    try:
        return tuple(int(v) if v != "*" else v for v in version.split("."))
    except ValueError:
        raise InvalidPyVersion(
            f"{version}: Prereleases or postreleases are not supported "
            "for python version specifers.")
Пример #3
0
 def _analyze_specifiers(self) -> None:
     # XXX: Prerelease or postrelease specifiers will fail here, but I guess we can
     # just ignore them for now.
     lower_bound, upper_bound = self.MIN_VERSION, self.MAX_VERSION
     excludes = set()  # type: Set[Tuple[Union[int, str], ...]]
     for spec in self:
         op, version = spec.operator, spec.version
         version = parse_version_tuple(version)
         if version[-1] == "*":
             if op == "==":
                 op = "~="
                 version = version[:-1] + (0,)
             elif op == "!=":
                 excludes.add(version)
                 continue
         if op != "~=":
             version = _complete_version(version)
         if op in ("==", "==="):
             lower_bound = version
             upper_bound = bump_version(version)
             break
         if op == "!=":
             excludes.add(version)
         elif op[0] == ">":
             if op == ">=":
                 new_lower = version
             else:
                 new_lower = bump_version(version)
             if new_lower > lower_bound:
                 lower_bound = new_lower
         elif op[0] == "<":
             if op == "<=":
                 new_upper = bump_version(version)
             else:
                 new_upper = version
             if new_upper < upper_bound:
                 upper_bound = new_upper
         elif op == "~=":
             new_lower = _complete_version(version)
             new_upper = bump_version(version, -2)
             if new_upper < upper_bound:
                 upper_bound = new_upper
             if new_lower > lower_bound:
                 lower_bound = new_lower
         else:
             raise InvalidPyVersion(
                 f"Unsupported version specifier: {spec.op}{spec.version}"
             )
     self._reorganize(lower_bound, upper_bound, excludes)
Пример #4
0
 def __init__(self, version: Union[Tuple[VersionBit, ...], str]) -> None:
     if isinstance(version, str):
         version_str = re.sub(r"(?<!\.)\*", ".*", version)
         try:
             version = cast(
                 Tuple[VersionBit, ...],
                 tuple(int(v) if v != "*" else v for v in version_str.split("."))[
                     :3
                 ],
             )
         except ValueError:
             raise InvalidPyVersion(
                 f"{version_str}: Prereleases or postreleases are not supported "
                 "for python version specifers."
             )
     self._version: Tuple[VersionBit, ...] = version
Пример #5
0
    def _analyze_specifiers(self) -> None:
        # XXX: Prerelease or postrelease specifiers will fail here, but I guess we can
        # just ignore them for now.
        lower_bound, upper_bound = Version.MIN, Version.MAX
        excludes: Set[Version] = set()
        for spec in self:
            op, version = _normalize_op_specifier(spec.operator, spec.version)

            if op in ("==", "==="):
                lower_bound = version
                upper_bound = version.bump()
                break
            if op == "!=":
                excludes.add(version)
            elif op[0] == ">":
                if op == ">=":
                    new_lower = version
                else:
                    new_lower = version.bump()
                if new_lower > lower_bound:
                    lower_bound = new_lower
            elif op[0] == "<":
                if op == "<=":
                    new_upper = version.bump()
                else:
                    new_upper = version
                if new_upper < upper_bound:
                    upper_bound = new_upper
            elif op == "~=":
                new_lower = version.complete()
                new_upper = version.bump(-2)
                if new_upper < upper_bound:
                    upper_bound = new_upper
                if new_lower > lower_bound:
                    lower_bound = new_lower
            else:
                raise InvalidPyVersion(
                    f"Unsupported version specifier: {op}{version}")
        self._rearrange(lower_bound, upper_bound, excludes)
Пример #6
0
def _normalize_op_specifier(op: str, version_str: str) -> Tuple[str, Version]:
    version = Version(version_str)
    if version.is_wildcard:
        if op == "==":
            op = "~="
            version[-1] = 0
        elif op == ">":  # >X.Y.* => >=X.Y+1.0
            op = ">="
            version = version.bump(-2)
        elif op in ("<", ">=", "<="):
            # <X.Y.* => <X.Y.0
            # >=X.Y.* => >=X.Y.0
            # <=X.Y.* => <X.Y.0
            version[-1] = 0
            if op == "<=":
                op = "<"
        elif op != "!=":
            raise InvalidPyVersion(f"Unsupported version specifier: {op}{version}")

    if op != "~=" and not (op == "!=" and version.is_wildcard):
        # Don't complete with .0 for ~=3.5 and !=3.4.*:
        version = version.complete()
    return op, version
Пример #7
0
 def __init__(self, version: Union[Tuple[VersionBit, ...], str]) -> None:
     if isinstance(version, str):
         version_str = re.sub(r"(?<!\.)\*", ".*", version)
         bits: List[VersionBit] = []
         for v in version_str.split(".")[:3]:
             try:
                 bits.append(int(v))
             except ValueError:
                 pre_m = PRE_RELEASE_SEGMENT_RE.match(v)
                 if v == "*":
                     bits.append("*")
                     break  # .* is only allowed at the end, per PEP 440
                 elif pre_m:
                     bits.append(int(pre_m.group("digit")))
                     pre_type = pre_m.group("type").lower()
                     pre_n = int(pre_m.group("n") or "0")
                     self.pre = (pre_type, pre_n)
                     break  # pre release version is only at the end
                 else:
                     raise InvalidPyVersion(
                         f"{version_str}: postreleases are not supported "
                         "for python version specifiers.")
         version = tuple(bits)
     self._version: Tuple[VersionBit, ...] = version
Пример #8
0
def do_use(
    project: Project,
    python: str = "",
    first: bool = False,
    ignore_remembered: bool = False,
) -> None:
    """Use the specified python version and save in project config.
    The python can be a version string or interpreter path.
    """
    if python:
        python = python.strip()

    def version_matcher(py_version: PythonInfo) -> bool:
        return project.python_requires.contains(str(py_version.version), True)

    if not project.cache_dir.exists():
        project.cache_dir.mkdir(parents=True)
    use_cache: JSONFileCache[str, str] = JSONFileCache(
        project.cache_dir / "use_cache.json"
    )
    selected_python: PythonInfo | None = None
    if python and not ignore_remembered:
        if use_cache.has_key(python):
            path = use_cache.get(python)
            cached_python = PythonInfo.from_path(path)
            if not cached_python.valid:
                project.core.ui.echo(
                    f"The last selection is corrupted. {path!r}",
                    fg="red",
                    err=True,
                )
            elif version_matcher(cached_python):
                project.core.ui.echo(
                    "Using the last selection, add '-i' to ignore it.",
                    fg="yellow",
                    err=True,
                )
                selected_python = cached_python

    if selected_python is None:
        found_interpreters = list(dict.fromkeys(project.find_interpreters(python)))
        matching_interperters = list(filter(version_matcher, found_interpreters))
        if not found_interpreters:
            raise NoPythonVersion("Python interpreter is not found on the system.")
        if not matching_interperters:
            project.core.ui.echo("Interpreters found but not matching:", err=True)
            for py in found_interpreters:
                project.core.ui.echo(f"  - {py.executable} ({py.identifier})", err=True)
            raise NoPythonVersion(
                "No python is found meeting the requirement "
                f"{termui.green('python' + str(project.python_requires))}"
            )
        if first or len(matching_interperters) == 1:
            selected_python = matching_interperters[0]
        else:
            project.core.ui.echo("Please enter the Python interpreter to use")
            for i, py_version in enumerate(matching_interperters):
                project.core.ui.echo(
                    f"{i}. {termui.green(str(py_version.executable))} "
                    f"({py_version.identifier})"
                )
            selection = click.prompt(
                "Please select:",
                type=click.Choice([str(i) for i in range(len(matching_interperters))]),
                default="0",
                show_choices=False,
            )
            selected_python = matching_interperters[int(selection)]
        if python:
            use_cache.set(python, selected_python.path.as_posix())

    if not selected_python.valid:
        path = str(selected_python.executable)
        raise InvalidPyVersion(f"Invalid Python interpreter: {path}")

    old_python = project.python if "python.path" in project.config else None
    project.core.ui.echo(
        "Using Python interpreter: {} ({})".format(
            termui.green(str(selected_python.executable)),
            selected_python.identifier,
        )
    )
    project.python = selected_python
    if (
        old_python
        and old_python.path != selected_python.path
        and not project.environment.is_global
    ):
        project.core.ui.echo(termui.cyan("Updating executable scripts..."))
        project.environment.update_shebangs(selected_python.executable.as_posix())
Пример #9
0
 def from_path(cls, path: str | Path) -> "PythonInfo":
     try:
         return cls.from_python_version(PythonVersion.from_path(str(path)))
     except InvalidPythonVersion as e:
         raise InvalidPyVersion(str(e)) from e