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 if python and not all(c.isdigit() for c in python.split(".")): if Path(python).exists(): python_path = Path(python).absolute().as_posix() else: python_path = shutil.which(python) if not python_path: raise NoPythonVersion(f"{python} is not a valid Python.") python_version = 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 = get_python_version(entry.path.as_posix(), True) pythons.append((entry.path.as_posix(), python_version)) 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) in enumerate(pythons): stream.echo(f"{i}. {stream.green(path)} ({python_version})") 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 = 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), python_version)) 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)
def init(project): """Initialize a pyproject.toml for PDM.""" python = click.prompt("Please enter the Python interpreter to use") actions.do_use(project, python) if project.pyproject_file.exists(): context.io.echo("{}".format( context.io.cyan("pyproject.toml already exists, update it now."))) else: context.io.echo("{}".format( context.io.cyan("Creating a pyproject.toml for PDM..."))) name = click.prompt(f"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(f"Author name", default=git_user) email = click.prompt(f"Author email", default=git_email) python_version = ".".join( map(str, get_python_version(project.environment.python_executable)[: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)
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 = ".".join( map(str, get_python_version(project.environment.python_executable)[: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)
def do_use(project: Project, python: str) -> None: """Use the specified python version and save in project config. The python can be a version string or interpreter path. """ if Path(python).is_absolute(): python_path = python else: python_path = shutil.which(python) if not python_path: finder = pythonfinder.Finder() try: python_path = finder.find_python_version( python).path.as_posix() except AttributeError: raise NoPythonVersion( f"Python {python} is not found on the system.") python_version = get_python_version(python_path, True) 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)) context.io.echo("Using Python interpreter: {} ({})".format( context.io.green(python_path), python_version)) project.config["python"] = Path(python_path).as_posix() project.config.save_config()
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, "-m", "pip", "--version"], capture_output=True) if proc.returncode == 0: # The pip has already been installed with the executable, just use it return [self.executable, "-m", "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, "-m", "pip"] # Otherwise, download a pip wheel from the Internet. pip_wheel = self._env.project.cache_dir / "pip.whl" if not pip_wheel.is_file(): _download_pip_wheel(pip_wheel) return [self.executable, str(pip_wheel / "pip")]
def test_init_command(project_no_init, invoke, mocker): mocker.patch( "pdm.cli.commands.get_user_email_from_git", return_value=("Testing", "*****@*****.**"), ) do_init = mocker.patch.object(actions, "do_init") result = invoke(["init"], input="python\ntest-project\n\n\n\n\n\n", obj=project_no_init) print(result.output) assert result.exit_code == 0 python_version = ".".join( map( str, get_python_version( project_no_init.environment.python_executable)[:2])) do_init.assert_called_with( project_no_init, "test-project", "0.0.0", "MIT", "Testing", "*****@*****.**", f">={python_version}", )
def do_info( project: Project, python: bool = False, show_project: bool = False, env: bool = False, ) -> None: """Show project information.""" python_path = project.environment.python_executable python_version = get_python_version(python_path) if not python and not show_project and not env: rows = [ ( context.io.cyan("Python Interpreter:", bold=True), python_path + f" ({python_version})", ), (context.io.cyan("Project Root:", bold=True), project.root.as_posix()), ] context.io.display_columns(rows) return if python: context.io.echo(python_path) if show_project: context.io.echo(project.root.as_posix()) if env: context.io.echo( json.dumps(project.environment.marker_environment, indent=2))
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 return shutil.which(command, path=os.getenv("PATH"))
def packages_path(self) -> Path: """The local packages path.""" pypackages = ( self.project.root / "__pypackages__" / ".".join(map(str, get_python_version(self.python_executable)[:2]))) 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
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() # 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))
def python_executable(self) -> str: """Get the Python interpreter path.""" path = None if self.config["python"]: path = self.config["python"] try: get_python_version(path) return path except Exception: pass path = None version = None # First try what `python` refers to. path = shutil.which("python") if path: version = get_python_version(path, True) else: finder = Finder() for python in finder.find_all_python_versions(): version = ".".join( map(str, get_python_version(python.path.as_posix()))) 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: context.io.echo("Using Python interpreter: {} ({})".format( context.io.green(path), version)) self.config["python"] = Path(path).as_posix() self.config.save_config() return path raise NoPythonVersion( "No Python that satisfies {} is found on the system.".format( self.python_requires))
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)) 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)
def python_executable(self) -> str: """Get the Python interpreter path.""" if self.config.get("python.path"): path = self.config["python.path"] try: get_python_version(path) return path except Exception: pass if PYENV_INSTALLED and self.config.get("python.use_pyenv", True): return os.path.join(PYENV_ROOT, "shims", "python") # 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: context.io.echo("Using Python interpreter: {} ({})".format( context.io.green(path), version)) self.config["python.path"] = Path(path).as_posix() self.config.save_config() return path raise NoPythonVersion( "No Python that satisfies {} is found on the system.".format( self.python_requires))
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)
def get_finder( self, sources: Optional[List[Source]] = None, ignore_requires_python: bool = False, ) -> shims.PackageFinder: """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. """ sources = sources or [] python_version = get_python_version(self.python_executable)[:2] finder = get_finder( sources, context.cache_dir.as_posix(), python_version, ignore_requires_python, ) yield finder finder.session.close()
def __init__(self, project: Project) -> None: super().__init__(project) self.python_requires = PySpecSet( "==" + get_python_version(self.python_executable, True))
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) [: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") 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)[: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
def get_working_set(self) -> WorkingSet: """Get the working set based on local packages directory.""" paths = self.get_paths() return WorkingSet([paths["platlib"]], python=get_python_version(self.python_executable))