Exemple #1
0
def do_use(project: Project, python: str, first: bool = False) -> None:
    """Use the specified python version and save in project config.
    The python can be a version string or interpreter path.
    """
    import pythonfinder

    python = python.strip()
    if python and not all(c.isdigit() for c in python.split(".")):
        if Path(python).exists():
            python_path = find_python_in_path(python)
        else:
            python_path = shutil.which(python)
        if not python_path:
            raise NoPythonVersion(f"{python} is not a valid Python.")
        python_version, is_64bit = get_python_version(python_path, True)
    else:
        finder = pythonfinder.Finder()
        pythons = []
        args = [int(v) for v in python.split(".") if v != ""]
        for i, entry in enumerate(finder.find_all_python_versions(*args)):
            python_version, is_64bit = get_python_version(entry.path.as_posix(), True)
            pythons.append((entry.path.as_posix(), python_version, is_64bit))
        if not pythons:
            raise NoPythonVersion(f"Python {python} is not available on the system.")

        if not first and len(pythons) > 1:
            for i, (path, python_version, is_64bit) in enumerate(pythons):
                stream.echo(
                    f"{i}. {stream.green(path)} "
                    f"({get_python_version_string(python_version, is_64bit)})"
                )
            selection = click.prompt(
                "Please select:",
                type=click.Choice([str(i) for i in range(len(pythons))]),
                default="0",
                show_choices=False,
            )
        else:
            selection = 0
        python_path, python_version, is_64bit = pythons[int(selection)]

    if not project.python_requires.contains(python_version):
        raise NoPythonVersion(
            "The target Python version {} doesn't satisfy "
            "the Python requirement: {}".format(python_version, project.python_requires)
        )
    stream.echo(
        "Using Python interpreter: {} ({})".format(
            stream.green(python_path),
            get_python_version_string(python_version, is_64bit),
        )
    )
    old_path = project.config.get("python.path")
    new_path = python_path
    project.project_config["python.path"] = Path(new_path).as_posix()
    if old_path and Path(old_path) != Path(new_path) and not project.is_global:
        stream.echo(stream.cyan("Updating executable scripts..."))
        project.environment.update_shebangs(new_path)
Exemple #2
0
    def python_executable(self) -> str:
        """Get the Python interpreter path."""
        config = self.config
        if self.project_config.get(
                "python.path") and not os.getenv("PDM_IGNORE_SAVED_PYTHON"):
            return self.project_config["python.path"]
        path = None
        if config["use_venv"]:
            path = get_venv_python(self.root)
            if path:
                stream.echo(
                    f"Virtualenv interpreter {stream.green(path)} is detected.",
                    err=True,
                    verbosity=stream.DETAIL,
                )
        if not path and PYENV_INSTALLED and config.get("python.use_pyenv",
                                                       True):
            path = Path(PYENV_ROOT, "shims", "python").as_posix()
        if not path:
            path = shutil.which("python")

        version = None
        if path:
            try:
                version, _ = get_python_version(path, True)
            except (FileNotFoundError, subprocess.CalledProcessError):
                version = None
        if not version or not self.python_requires.contains(version):
            finder = Finder()
            for python in finder.find_all_python_versions():
                version, _ = get_python_version(python.path.as_posix(), True)
                if self.python_requires.contains(version):
                    path = python.path.as_posix()
                    break
            else:
                version = ".".join(map(str, sys.version_info[:3]))
                if self.python_requires.contains(version):
                    path = sys.executable
        if path:
            if os.path.normcase(path) == os.path.normcase(sys.executable):
                # Refer to the base interpreter to allow for venvs
                path = getattr(sys, "_base_executable", sys.executable)
            stream.echo(
                "Using Python interpreter: {} ({})".format(
                    stream.green(path), version),
                err=True,
            )
            if not os.getenv("PDM_IGNORE_SAVED_PYTHON"):
                self.project_config["python.path"] = Path(path).as_posix()
            return path
        raise NoPythonVersion(
            "No Python that satisfies {} is found on the system.".format(
                self.python_requires))
Exemple #3
0
def do_info(
    project: Project,
    python: bool = False,
    show_project: bool = False,
    env: bool = False,
) -> None:
    """Show project information."""
    check_project_file(project)
    python_path = project.python_executable
    python_version, is_64bit = get_python_version(python_path, True)
    if not python and not show_project and not env:
        rows = [
            (termui.cyan("PDM version:", bold=True), project.core.version),
            (
                termui.cyan("Python Interpreter:", bold=True),
                python_path +
                f" ({get_python_version_string(python_version, is_64bit)})",
            ),
            (termui.cyan("Project Root:", bold=True), project.root.as_posix()),
        ]
        project.core.ui.display_columns(rows)
        return

    if python:
        project.core.ui.echo(python_path)
    if show_project:
        project.core.ui.echo(project.root.as_posix())
    if env:
        project.core.ui.echo(
            json.dumps(project.environment.marker_environment, indent=2))
Exemple #4
0
    def get_finder(
        self,
        sources: Optional[List[Source]] = None,
        ignore_requires_python: bool = False,
    ) -> Generator[pip_shims.PackageFinder, None, None]:
        """Return the package finder of given index sources.

        :param sources: a list of sources the finder should search in.
        :param ignore_requires_python: whether to ignore the python version constraint.
        """
        if sources is None:
            sources = self.project.sources
        for source in sources:
            source["url"] = expand_env_vars_in_auth(source["url"])

        python_version, _ = get_python_version(self.python_executable,
                                               digits=2)
        finder = get_finder(
            sources,
            self.project.cache_dir.as_posix(),
            python_version,
            ignore_requires_python,
        )
        # Reuse the auth across sessions to avoid prompting repeatly.
        finder.session.auth = self.auth
        yield finder
        finder.session.close()
Exemple #5
0
    def handle(self, project: Project, options: argparse.Namespace) -> None:
        if project.pyproject_file.exists():
            stream.echo(
                "{}".format(
                    stream.cyan("pyproject.toml already exists, update it now.")
                )
            )
        else:
            stream.echo(
                "{}".format(stream.cyan("Creating a pyproject.toml for PDM..."))
            )
        python = click.prompt(
            "Please enter the Python interpreter to use", default="", show_default=False
        )
        actions.do_use(project, python)
        name = click.prompt("Project name", default=project.root.name)
        version = click.prompt("Project version", default="0.0.0")
        license = click.prompt("License(SPDX name)", default="MIT")

        git_user, git_email = get_user_email_from_git()
        author = click.prompt("Author name", default=git_user)
        email = click.prompt("Author email", default=git_email)
        python_version, _ = get_python_version(
            project.environment.python_executable, True, 2
        )
        python_requires = click.prompt(
            "Python requires('*' to allow any)", default=f">={python_version}"
        )

        actions.do_init(project, name, version, license, author, email, python_requires)
        actions.ask_for_import(project)
Exemple #6
0
 def _get_pip_command(self) -> List[str]:
     """Get a pip command that has pip installed.
     E.g: ['python', '-m', 'pip']
     """
     python_version, _ = get_python_version(self.executable)
     proc = subprocess.run([self.executable, "-Im", "pip", "--version"],
                           capture_output=True)
     if proc.returncode == 0:
         # The pip has already been installed with the executable, just use it
         return [self.executable, "-Im", "pip"]
     if python_version[0] == 3:
         # Use the ensurepip to provision one.
         try:
             self.subprocess_runner([
                 self.executable, "-Im", "ensurepip", "--upgrade",
                 "--default-pip"
             ])
         except BuildError:
             pass
         else:
             return [self.executable, "-Im", "pip"]
     # Otherwise, download a pip wheel from the Internet.
     pip_wheel = self._env.project.cache_dir / "pip.whl"
     if not pip_wheel.is_file():
         self._download_pip_wheel(pip_wheel)
     return [self.executable, str(pip_wheel / "pip")]
Exemple #7
0
    def handle(self, project: Project, options: argparse.Namespace) -> None:
        if project.pyproject_file.exists():
            project.core.ui.echo("{}".format(
                termui.cyan("pyproject.toml already exists, update it now.")))
        else:
            project.core.ui.echo("{}".format(
                termui.cyan("Creating a pyproject.toml for PDM...")))
        actions.do_use(project)
        is_library = click.confirm(
            "Is the project a library that will be upload to PyPI?", )
        if is_library:
            name = click.prompt("Project name", default=project.root.name)
            version = click.prompt("Project version", default="0.1.0")
        else:
            name, version = "", ""
        license = click.prompt("License(SPDX name)", default="MIT")

        git_user, git_email = get_user_email_from_git()
        author = click.prompt("Author name", default=git_user)
        email = click.prompt("Author email", default=git_email)
        python_version, _ = get_python_version(project.python_executable, True,
                                               2)
        python_requires = click.prompt("Python requires('*' to allow any)",
                                       default=f">={python_version}")

        actions.do_init(project, name, version, license, author, email,
                        python_requires)
        actions.ask_for_import(project)
Exemple #8
0
 def get_working_set(self) -> WorkingSet:
     """Get the working set based on local packages directory."""
     paths = self.get_paths()
     return WorkingSet(
         [paths["platlib"], paths["purelib"]],
         python=get_python_version(self.python_executable)[0],
     )
Exemple #9
0
    def python_executable(self) -> str:
        """Get the Python interpreter path."""
        config = self.project.config
        if config.get("python.path"):
            return config["python.path"]
        if PYENV_INSTALLED and config.get("python.use_pyenv", True):
            return os.path.join(PYENV_ROOT, "shims", "python")
        if "VIRTUAL_ENV" in os.environ:
            stream.echo(
                "An activated virtualenv is detected, reuse the interpreter now.",
                err=True,
                verbosity=stream.DETAIL,
            )
            return get_venv_python(self.project.root)

        # First try what `python` refers to.
        path = shutil.which("python")
        version = None
        if path:
            version, _ = get_python_version(path, True)
        if not version or not self.python_requires.contains(version):
            finder = Finder()
            for python in finder.find_all_python_versions():
                version, _ = get_python_version(python.path.as_posix(), True)
                if self.python_requires.contains(version):
                    path = python.path.as_posix()
                    break
            else:
                version = ".".join(map(str, sys.version_info[:3]))
                if self.python_requires.contains(version):
                    path = sys.executable
        if path:
            stream.echo(
                "Using Python interpreter: {} ({})".format(stream.green(path), version)
            )
            self.project.project_config["python.path"] = Path(path).as_posix()
            return path
        raise NoPythonVersion(
            "No Python that satisfies {} is found on the system.".format(
                self.python_requires
            )
        )
Exemple #10
0
 def environment(self) -> Environment:
     if self.is_global:
         env = GlobalEnvironment(self)
         # Rewrite global project's python requires to be
         # compatible with the exact version
         env.python_requires = PySpecSet(
             "==" + get_python_version(self.python_executable, True)[0])
         return env
     if self.config["use_venv"] and is_venv_python(self.python_executable):
         # Only recognize venv created by python -m venv and virtualenv>20
         return GlobalEnvironment(self)
     return Environment(self)
Exemple #11
0
 def which(self, command: str) -> str:
     """Get the full path of the given executable against this environment."""
     if not os.path.isabs(command) and command.startswith("python"):
         python = os.path.splitext(command)[0]
         version = python[6:]
         this_version, _ = get_python_version(self.python_executable, True)
         if not version or this_version.startswith(version):
             return self.python_executable
     # Fallback to use shutil.which to find the executable
     this_path = self.get_paths()["scripts"]
     python_root = os.path.dirname(self.python_executable)
     new_path = os.pathsep.join([python_root, this_path, os.getenv("PATH", "")])
     return shutil.which(command, path=new_path)
Exemple #12
0
 def packages_path(self) -> Path:
     """The local packages path."""
     version, is_64bit = get_python_version(self.python_executable, True, 2)
     pypackages = (self.project.root / "__pypackages__" /
                   get_python_version_string(version, is_64bit))
     if not pypackages.exists() and not is_64bit:
         compatible_packages = pypackages.parent / get_python_version_string(
             version, True)
         if compatible_packages.exists():
             pypackages = compatible_packages
     scripts = "Scripts" if os.name == "nt" else "bin"
     for subdir in [scripts, "include", "lib"]:
         pypackages.joinpath(subdir).mkdir(exist_ok=True, parents=True)
     return pypackages
Exemple #13
0
 def environment(self) -> Environment:
     if self.is_global:
         env = GlobalEnvironment(self)
         # Rewrite global project's python requires to be
         # compatible with the exact version
         env.python_requires = PySpecSet(
             "==" + get_python_version(env.python_executable, True)[0])
         return env
     if self.config["use_venv"]:
         venv_python = get_venv_python(self.root)
         if venv_python:
             self.project_config["python.path"] = venv_python
             return GlobalEnvironment(self)
     return Environment(self)
Exemple #14
0
def test_init_command(project_no_init, invoke, mocker):
    mocker.patch(
        "pdm.cli.commands.init.get_user_email_from_git",
        return_value=("Testing", "*****@*****.**"),
    )
    do_init = mocker.patch.object(actions, "do_init")
    result = invoke(["init"], input="\n\n\n\n\n\n", obj=project_no_init)
    assert result.exit_code == 0
    python_version, _ = get_python_version(project_no_init.python_executable, True, 2)
    do_init.assert_called_with(
        project_no_init,
        "",
        "",
        "MIT",
        "Testing",
        "*****@*****.**",
        f">={python_version}",
    )
Exemple #15
0
 def environment(self) -> Environment:
     if self.is_global:
         env = GlobalEnvironment(self)
         # Rewrite global project's python requires to be
         # compatible with the exact version
         env.python_requires = PySpecSet(
             "==" + get_python_version(env.python_executable, True)[0])
         return env
     if not self.project_config.get(
             "python.path") and self.config["use_venv"]:
         venv_python = get_venv_python(self.root)
         if venv_python:
             self.project_config["python.path"] = venv_python
     if (self.config["use_venv"] and self.project_config.get("python.path")
             and Path(self.project_config.get("python.path")
                      ).parent.parent.joinpath("pyvenv.cfg").exists()):
         # Only recognize venv created by python -m venv and virtualenv>20
         return GlobalEnvironment(self)
     return Environment(self)
Exemple #16
0
    def build(
        self,
        ireq: pip_shims.InstallRequirement,
        hashes: Optional[Dict[str, str]] = None,
        allow_all: bool = True,
    ) -> str:
        """Build egg_info directory for editable candidates and a wheel for others.

        :param ireq: the InstallRequirment of the candidate.
        :param hashes: a dictionary of filename: hash_value to check against downloaded
        artifacts.
        :param allow_all: Allow building incompatible wheels.
        :returns: The full path of the built artifact.
        """
        kwargs = self._make_building_args(ireq)
        wheel_cache = self.project.make_wheel_cache()
        with self.get_finder() as finder:
            with allow_all_wheels(allow_all):
                # temporarily allow all wheels to get a link.
                populate_link(finder, ireq, False)
                if hashes is None:
                    cache_entry = wheel_cache.get_cache_entry(
                        ireq.link,
                        ireq.req.project_name,
                        pip_shims.get_supported(
                            version="".join(
                                map(
                                    str,
                                    get_python_version(self.python_executable)[0][:2],
                                )
                            )
                        ),
                    )
                    if cache_entry is not None:
                        stream.logger.debug(
                            "Using cached wheel link: %s", cache_entry.link
                        )
                        ireq.link = cache_entry.link
            if not ireq.editable and not ireq.req.name:
                ireq.source_dir = kwargs["build_dir"]
            else:
                ireq.ensure_has_source_dir(kwargs["build_dir"])

            download_dir = kwargs["download_dir"]
            only_download = False
            if ireq.link.is_wheel:
                download_dir = kwargs["wheel_download_dir"]
                only_download = True
            if hashes:
                ireq.hash_options = convert_hashes(hashes)
            if not (ireq.editable and ireq.req.is_local_dir):
                downloader = pip_shims.Downloader(finder.session, "off")
                if ireq.link.is_vcs:
                    ireq.link = pip_shims.Link(expand_env_vars_in_auth(ireq.link.url))
                downloaded = pip_shims.unpack_url(
                    ireq.link,
                    ireq.source_dir,
                    downloader,
                    download_dir,
                    ireq.hashes(False),
                )
                # Preserve the downloaded file so that it won't be cleared.
                if downloaded and only_download:
                    try:
                        shutil.copy(downloaded.path, download_dir)
                    except shutil.SameFileError:
                        pass

            if ireq.link.is_wheel:
                # If the file is a wheel, should be already present under download dir.
                return (self.project.cache("wheels") / ireq.link.filename).as_posix()
            else:
                # Check the built wheel cache again after hashes are resolved.
                cache_entry = wheel_cache.get_cache_entry(
                    ireq.link,
                    ireq.req.project_name,
                    pip_shims.get_supported(
                        version="".join(
                            map(str, get_python_version(self.python_executable)[0][:2])
                        )
                    ),
                )
                if cache_entry is not None:
                    stream.logger.debug("Using cached wheel link: %s", cache_entry.link)
                    return cache_entry.link.file_path

            # Otherwise, now all source is prepared, build it.
            with EnvBuilder(ireq.unpacked_source_directory, self) as builder:
                if ireq.editable:
                    ret = builder.build_egg_info(kwargs["build_dir"])
                    ireq.metadata_directory = ret
                else:
                    should_cache = False
                    if ireq.link.is_vcs:
                        vcs = pip_shims.VcsSupport()
                        vcs_backend = vcs.get_backend_for_scheme(ireq.link.scheme)
                        if vcs_backend.is_immutable_rev_checkout(
                            ireq.link.url, ireq.source_dir
                        ):
                            should_cache = True
                    else:
                        base, _ = ireq.link.splitext()
                        if _egg_info_re.search(base) is not None:
                            # Determine whether the string looks like an egg_info.
                            should_cache = True
                    output_dir = (
                        wheel_cache.get_path_for_link(ireq.link)
                        if should_cache
                        else kwargs["build_dir"]
                    )
                    if not os.path.exists(output_dir):
                        os.makedirs(output_dir, exist_ok=True)
                    ret = builder.build_wheel(output_dir)
            return ret
Exemple #17
0
    def build(
        self,
        ireq: pip_shims.InstallRequirement,
        hashes: Optional[Dict[str, str]] = None,
        allow_all: bool = True,
    ) -> str:
        """Build egg_info directory for editable candidates and a wheel for others.

        :param ireq: the InstallRequirment of the candidate.
        :param hashes: a dictionary of filename: hash_value to check against downloaded
        artifacts.
        :param allow_all: Allow building incompatible wheels.
        :returns: The full path of the built artifact.
        """
        build_dir = self._get_build_dir(ireq)
        wheel_cache = self.project.make_wheel_cache()
        supported_tags = pip_shims.get_supported("".join(
            map(str,
                get_python_version(self.python_executable, digits=2)[0])))
        with self.get_finder(ignore_requires_python=True) as finder:
            with allow_all_wheels(allow_all):
                # temporarily allow all wheels to get a link.
                populate_link(finder, ireq, False)
            ireq.link = pip_shims.Link(
                expand_env_vars_in_auth(
                    ireq.link.url.replace(
                        "${PROJECT_ROOT}",
                        self.project.root.as_posix().lstrip("/"))))
            if hashes is None and not ireq.editable:
                # If hashes are not given and cache is hit, replace the link with the
                # cached one. This can speed up by skipping the download and build.
                cache_entry = wheel_cache.get_cache_entry(
                    ireq.link,
                    ireq.req.project_name,
                    supported_tags,
                )
                if cache_entry is not None:
                    termui.logger.debug("Using cached wheel link: %s",
                                        cache_entry.link)
                    ireq.link = cache_entry.link
            if not ireq.editable and not ireq.req.name:
                ireq.source_dir = build_dir
            else:
                ireq.ensure_has_source_dir(build_dir)

            if hashes:
                ireq.hash_options = convert_hashes(hashes)
            if not (ireq.editable and ireq.req.is_local_dir):
                downloader = pip_shims.Downloader(finder.session, "off")
                downloaded = pip_shims.unpack_url(
                    ireq.link,
                    ireq.source_dir,
                    downloader,
                    hashes=ireq.hashes(False),
                )

                if ireq.link.is_wheel:
                    # If the file is a wheel, return the downloaded file directly.
                    return downloaded.path

        # Check the built wheel cache again after hashes are resolved.
        if not ireq.editable:
            cache_entry = wheel_cache.get_cache_entry(
                ireq.link,
                ireq.req.project_name,
                supported_tags,
            )
            if cache_entry is not None:
                termui.logger.debug("Using cached wheel link: %s",
                                    cache_entry.link)
                return cache_entry.link.file_path

        # Otherwise, as all source is already prepared, build it.
        with EnvBuilder(ireq.unpacked_source_directory, self) as builder:
            if ireq.editable:
                ret = builder.build_egg_info(build_dir)
                ireq.metadata_directory = ret
                return ret
            should_cache = False
            if ireq.link.is_vcs:
                vcs = pip_shims.VcsSupport()
                vcs_backend = vcs.get_backend_for_scheme(ireq.link.scheme)
                if vcs_backend.is_immutable_rev_checkout(
                        ireq.link.url, ireq.source_dir):
                    should_cache = True
            else:
                base, _ = ireq.link.splitext()
                if _egg_info_re.search(base) is not None:
                    # Determine whether the string looks like an egg_info.
                    should_cache = True
            output_dir = (wheel_cache.get_path_for_link(ireq.link)
                          if should_cache else build_dir)
            if not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)
            return builder.build_wheel(output_dir)