Exemple #1
0
def check_project_file(project: Project) -> None:
    """Check the existence of the project file and throws an error on failure."""
    if not project.tool_settings:
        raise ProjectError(
            "The pyproject.toml has not been initialized yet. You can do this "
            "by running {}.".format(stream.green("'pdm init'"))
        )
Exemple #2
0
def do_list(project: Project,
            graph: bool = False,
            reverse: bool = False) -> None:
    """Display a list of packages installed in the local packages directory.

    :param project: the project instance.
    :param graph: whether to display a graph.
    :param reverse: wheter to display reverse graph.
    """
    from pdm.cli.utils import (
        build_dependency_graph,
        format_dependency_graph,
        format_reverse_dependency_graph,
    )

    check_project_file(project)
    working_set = project.environment.get_working_set()
    if reverse and not graph:
        raise PdmUsageError("--reverse must be used with --graph")
    if graph:
        with project.environment.activate():
            dep_graph = build_dependency_graph(working_set)
        if reverse:
            graph = format_reverse_dependency_graph(project, dep_graph)
        else:
            graph = format_dependency_graph(project, dep_graph)
        stream.echo(graph)
    else:
        rows = [(stream.green(k, bold=True), format_dist(v))
                for k, v in sorted(working_set.items())]
        stream.display_columns(rows, ["Package", "Version"])
Exemple #3
0
 def generate_rows(self) -> Iterator[Tuple[str, str]]:
     if self.legacy:
         yield from self._legacy_generate_rows()
         return
     yield stream.cyan("Name:"), self._data["name"]
     yield stream.cyan("Latest version:"), self._data["version"]
     if self.latest_stable_version:
         yield (stream.cyan("Latest stable version:"),
                self.latest_stable_version)
     if self.installed_version:
         yield (stream.green("Installed version:"), self.installed_version)
     yield stream.cyan("Summary:"), self._data.get("summary", "")
     contacts = (self._data.get("extensions", {}).get("python.details",
                                                      {}).get("contacts"))
     if contacts:
         author_contact = next(
             iter(c for c in contacts if c["role"] == "author"), {})
         yield stream.cyan("Author:"), author_contact.get("name", "")
         yield stream.cyan("Author email:"), author_contact.get("email", "")
     yield stream.cyan("License:"), self._data.get("license", "")
     yield stream.cyan("Homepage:"), self._data.get("extensions", {}).get(
         "python.details", {}).get("project_urls", {}).get("Home", "")
     yield stream.cyan("Project URLs:"), self._data.get("project_url", "")
     yield stream.cyan("Platform:"), self._data.get("platform", "")
     yield stream.cyan("Keywords:"), ", ".join(
         self._data.get("keywords", []))
Exemple #4
0
def set_env_in_reg(env_name: str, value: str) -> None:
    """Manipulate the WinReg, and add value to the
    environment variable if exists or create new.
    """
    import winreg

    value = os.path.normcase(value)

    with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
        with winreg.OpenKey(root, "Environment", 0,
                            winreg.KEY_ALL_ACCESS) as env_key:
            try:
                old_value, type_ = winreg.QueryValueEx(env_key, env_name)
                if value in [
                        os.path.normcase(item)
                        for item in old_value.split(os.pathsep)
                ]:
                    return
            except FileNotFoundError:
                old_value, type_ = "", winreg.REG_EXPAND_SZ
            new_value = ";".join(old_value, value) if old_value else value
            try:
                winreg.SetValueEx(env_key, env_name, 0, type_, new_value)
            except PermissionError:
                stream.echo(
                    stream.red(
                        "Permission denied, please run the terminal as administrator."
                    ),
                    err=True,
                )
                sys.exit(1)
    stream.echo(
        stream.green("The environment variable has been saved, "
                     "please restart the session to take effect."))
Exemple #5
0
def do_remove(
    project: Project,
    dev: bool = False,
    section: Optional[str] = None,
    sync: bool = True,
    packages: Sequence[str] = (),
):
    """Remove packages from working set and pyproject.toml

    :param project: The project instance
    :param dev: Remove package from dev-dependencies
    :param section: Remove package from given section
    :param sync: Whether perform syncing action
    :param packages: Package names to be removed
    :return: None
    """
    check_project_file(project)
    if not packages:
        raise PdmUsageError("Must specify at least one package to remove.")
    section = "dev" if dev else section or "default"
    if section not in list(project.iter_sections()):
        raise ProjectError(f"No {section} dependencies given in pyproject.toml.")

    deps = project.get_pyproject_dependencies(section)
    stream.echo(
        f"Removing packages from {section} dependencies: "
        + ", ".join(str(stream.green(name, bold=True)) for name in packages)
    )
    for name in packages:
        req = parse_requirement(name)
        matched_indexes = sorted(
            (i for i, r in enumerate(deps) if req.matches(r, False)), reverse=True
        )
        if not matched_indexes:
            raise ProjectError(
                "{} does not exist in {} dependencies.".format(
                    stream.green(name, bold=True), section
                )
            )
        for i in matched_indexes:
            del deps[i]

    project.write_pyproject()
    do_lock(project, "reuse")
    if sync:
        do_sync(project, sections=(section,), default=False, clean=True)
Exemple #6
0
 def handle(self, project: Project, options: argparse.Namespace) -> None:
     with project.environment.activate():
         expanded_command = project.environment.which(options.command)
         if not expanded_command:
             raise PdmUsageError(
                 "Command {} is not found on your PATH.".format(
                     stream.green(f"'{options.command}'")))
         sys.exit(subprocess.call([expanded_command] + list(options.args)))
Exemple #7
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 #8
0
def do_add(
    project: Project,
    dev: bool = False,
    section: Optional[str] = None,
    sync: bool = True,
    save: str = "compatible",
    strategy: str = "reuse",
    editables: Iterable[str] = (),
    packages: Iterable[str] = (),
) -> None:
    """Add packages and install

    :param project: the project instance
    :param dev: add to dev dependencies seciton
    :param section: specify section to be add to
    :param sync: whether to install added packages
    :param save: save strategy
    :param strategy: update strategy
    :param editables: editable requirements
    :param packages: normal requirements
    """
    check_project_file(project)
    if not editables and not packages:
        raise PdmUsageError("Must specify at least one package or editable package.")
    section = "dev" if dev else section or "default"
    tracked_names = set()
    requirements = {}
    for r in [parse_requirement(line, True) for line in editables] + [
        parse_requirement(line) for line in packages
    ]:
        key = r.identify()
        r.from_section = section
        tracked_names.add(key)
        requirements[key] = r
    stream.echo(
        f"Adding packages to {section} dependencies: "
        + ", ".join(stream.green(key or "", bold=True) for key in requirements)
    )
    all_dependencies = project.all_dependencies
    all_dependencies.setdefault(section, {}).update(requirements)
    reqs = [r for deps in all_dependencies.values() for r in deps.values()]
    resolved = do_lock(project, strategy, tracked_names, reqs)

    # Update dependency specifiers and lockfile hash.
    save_version_specifiers(requirements, resolved, save)
    project.add_dependencies(requirements)
    lockfile = project.lockfile
    project.write_lockfile(lockfile, False)

    if sync:
        do_sync(
            project,
            sections=(section,),
            dev=False,
            default=False,
            dry_run=False,
            clean=False,
        )
Exemple #9
0
def do_remove(
        project: Project,
        dev: bool = False,
        section: Optional[str] = None,
        sync: bool = True,
        packages: Sequence[str] = (),
):
    """Remove packages from working set and pyproject.toml

    :param project: The project instance
    :param dev: Remove package from dev-dependencies
    :param section: Remove package from given section
    :param sync: Whether perform syncing action
    :param packages: Package names to be removed
    :return: None
    """
    check_project_file(project)
    if not packages:
        raise PdmUsageError("Must specify at least one package to remove.")
    section = "dev" if dev else section or "default"
    toml_section = f"{section}-dependencies" if section != "default" else "dependencies"
    if toml_section not in project.tool_settings:
        raise ProjectError(
            f"No such section {stream.yellow(toml_section)} in pyproject.toml."
        )
    deps = project.tool_settings[toml_section]
    stream.echo(f"Removing packages from {section} dependencies: " + ", ".join(
        str(stream.green(name, bold=True)) for name in packages))
    for name in packages:
        matched_name = next(
            filter(
                lambda k: safe_name(k).lower() == safe_name(name).lower(),
                deps.keys(),
            ),
            None,
        )
        if not matched_name:
            raise ProjectError("{} does not exist in {} dependencies.".format(
                stream.green(name, bold=True), section))
        del deps[matched_name]

    project.write_pyproject()
    do_lock(project, "reuse")
    if sync:
        do_sync(project, sections=(section, ), default=False, clean=True)
Exemple #10
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 #11
0
 def _show_list(self, project: Project) -> None:
     if not project.scripts:
         return
     columns = ["Name", "Type", "Script", "Description"]
     result = []
     for name, script in project.scripts.items():
         if name == "_":
             continue
         kind, value, options = self._normalize_script(script)
         result.append(
             (stream.green(name), kind, value, options.get("help", "")))
     stream.display_columns(result, columns)
Exemple #12
0
    def _run_command(
        project: Project,
        args: Union[List[str], str],
        shell: bool = False,
        env: Optional[Dict[str, str]] = None,
        env_file: Optional[str] = None,
    ) -> None:
        if "PYTHONPATH" in os.environ:
            pythonpath = os.pathsep.join(
                [PEP582_PATH, os.getenv("PYTHONPATH")])
        else:
            pythonpath = PEP582_PATH
        project_env = project.environment
        this_path = project_env.get_paths()["scripts"]
        python_root = os.path.dirname(project.python_executable)
        new_path = os.pathsep.join(
            [python_root, this_path,
             os.getenv("PATH", "")])
        os.environ.update({"PYTHONPATH": pythonpath, "PATH": new_path})
        if project_env.packages_path:
            os.environ.update(
                {"PEP582_PACKAGES": str(project_env.packages_path)})
        if env_file:
            import dotenv

            stream.echo(f"Loading .env file: {stream.green(env_file)}",
                        err=True)
            dotenv.load_dotenv(project.root.joinpath(env_file).as_posix(),
                               override=True)
        if env:
            os.environ.update(env)
        if shell:
            sys.exit(subprocess.call(os.path.expandvars(args), shell=True))

        command, *args = args
        expanded_command = project_env.which(command)
        if not expanded_command:
            raise PdmUsageError("Command {} is not found on your PATH.".format(
                stream.green(f"'{command}'")))
        expanded_command = os.path.expanduser(
            os.path.expandvars(expanded_command))
        expanded_args = [
            os.path.expandvars(arg) for arg in [expanded_command] + args
        ]
        if os.name == "nt" or "CI" in os.environ:
            # In order to make sure pytest is playing well,
            # don't hand over the process under a testing environment.
            sys.exit(subprocess.call(expanded_args))
        else:
            os.execv(expanded_command, expanded_args)
Exemple #13
0
 def progressbar(self, label: str, total: int):
     bar = progressbar(
         length=total,
         fill_char=stream.green(self.BAR_FILLED_CHAR),
         empty_char=self.BAR_EMPTY_CHAR,
         show_percent=False,
         show_pos=True,
         label=label,
         bar_template="%(label)s %(bar)s %(info)s",
     )
     if self.parallel:
         executor = ThreadPoolExecutor()
     else:
         executor = DummyExecutor()
     with executor:
         yield bar, executor
Exemple #14
0
def do_list(project: Project, graph: bool = False) -> None:
    """Display a list of packages installed in the local packages directory.

    :param project: the project instance.
    :param graph: whether to display a graph.
    """
    from pdm.cli.utils import build_dependency_graph, format_dependency_graph

    check_project_file(project)
    working_set = project.environment.get_working_set()
    if graph:
        with project.environment.activate():
            dep_graph = build_dependency_graph(working_set)
        stream.echo(format_dependency_graph(dep_graph))
    else:
        rows = [(stream.green(k, bold=True), format_dist(v))
                for k, v in sorted(working_set.items())]
        stream.display_columns(rows, ["Package", "Version"])
Exemple #15
0
def print_results(hits, working_set, terminal_width=None):
    if not hits:
        return
    name_column_width = (
        max(
            [
                len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
                for hit in hits
            ]
        )
        + 4
    )

    for hit in hits:
        name = hit["name"]
        summary = hit["summary"] or ""
        latest = highest_version(hit.get("versions", ["-"]))
        if terminal_width is not None:
            target_width = terminal_width - name_column_width - 5
            if target_width > 10:
                # wrap and indent summary to fit terminal
                summary = textwrap.wrap(summary, target_width)
                summary = ("\n" + " " * (name_column_width + 2)).join(summary)
        current_width = len(name) + len(latest) + 4
        spaces = " " * (name_column_width - current_width)
        line = "{name} ({latest}){spaces} - {summary}".format(
            name=stream.green(name, bold=True),
            latest=stream.yellow(latest),
            spaces=spaces,
            summary=summary,
        )
        try:
            stream.echo(line)
            if safe_name(name).lower() in working_set:
                dist = working_set[safe_name(name).lower()]
                if dist.version == latest:
                    stream.echo("  INSTALLED: %s (latest)" % dist.version)
                else:
                    stream.echo("  INSTALLED: %s" % dist.version)
                    stream.echo("  LATEST:    %s" % latest)
        except UnicodeEncodeError:
            pass
Exemple #16
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 #17
0
def migrate_pyproject(project: Project):
    """Migrate the legacy pyproject format to PEP 621"""

    if (not project.pyproject_file.exists()
            or not FORMATS["legacy"].check_fingerprint(project,
                                                       project.pyproject_file)
            or "project" in project.pyproject):
        return

    stream.echo(
        stream.yellow(
            "Legacy [tool.pdm] metadata detected, migrating to PEP 621..."),
        err=True,
    )
    do_import(project, project.pyproject_file, "legacy")
    stream.echo(
        stream.green("pyproject.toml") + stream.yellow(
            " has been migrated to PEP 621 successfully. "
            "Now you can safely delete the legacy metadata under [tool.pdm] table."
        ),
        err=True,
    )
Exemple #18
0
 def _legacy_generate_rows(self) -> Iterator[Tuple[str, str]]:
     yield stream.cyan("Name:"), self._data["Name"]
     yield stream.cyan("Latest version:"), self._data["Version"]
     if self.latest_stable_version:
         yield (stream.cyan("Latest stable version:"),
                self.latest_stable_version)
     if self.installed_version:
         yield (stream.green("Installed version:"), self.installed_version)
     yield stream.cyan("Summary:"), self._data.get("Summary", "")
     yield stream.cyan("Author:"), self._data.get("Author", "")
     yield stream.cyan("Author email:"), self._data.get("Author-email", "")
     yield stream.cyan("License:"), self._data.get("License", "")
     yield stream.cyan("Homepage:"), self._data.get("Home-page", "")
     if self._data.get("Project-URL"):
         lines = [
             ":".join(parts) for parts in self._data.get("Project-URL")
         ]
         yield stream.cyan("Project URLs:"), lines[0]
         for line in lines[1:]:
             yield "", line
     yield stream.cyan("Platform:"), ", ".join(
         self._data.get("Platform", []))
     yield stream.cyan("Keywords:"), ", ".join(
         self._data.get("Keywords", []))
Exemple #19
0
def do_update(
        project: Project,
        dev: bool = False,
        sections: Sequence[str] = (),
        default: bool = True,
        strategy: str = "reuse",
        save: str = "compatible",
        unconstrained: bool = False,
        packages: Sequence[str] = (),
) -> None:
    """Update specified packages or all packages

    :param project: The project instance
    :param dev: whether to update dev dependencies
    :param sections: update speicified sections
    :param default: update default
    :param strategy: update strategy (reuse/eager)
    :param save: save strategy (compatible/exact/wildcard)
    :param unconstrained: ignore version constraint
    :param packages: specified packages to update
    :return: None
    """
    check_project_file(project)
    if len(packages) > 0 and (len(sections) > 1 or not default):
        raise PdmUsageError(
            "packages argument can't be used together with multple -s or --no-default."
        )
    if not packages:
        if unconstrained:
            raise PdmUsageError(
                "--unconstrained must be used with package names given.")
        # pdm update with no packages given, same as 'lock' + 'sync'
        do_lock(project)
        do_sync(project, sections, dev, default, clean=False)
        return
    section = sections[0] if sections else ("dev" if dev else "default")
    all_dependencies = project.all_dependencies
    dependencies = all_dependencies[section]
    updated_deps = {}
    tracked_names = set()
    for name in packages:
        matched_name = next(
            filter(
                lambda k: safe_name(strip_extras(k)[0]).lower() == safe_name(
                    name).lower(),
                dependencies.keys(),
            ),
            None,
        )
        if not matched_name:
            raise ProjectError("{} does not exist in {} dependencies.".format(
                stream.green(name, bold=True), section))
        if unconstrained:
            dependencies[matched_name].specifier = get_specifier("")
        tracked_names.add(matched_name)
        updated_deps[matched_name] = dependencies[matched_name]
    stream.echo("Updating packages: {}.".format(", ".join(
        stream.green(v, bold=True) for v in tracked_names)))
    reqs = [r for deps in all_dependencies.values() for r in deps.values()]
    resolved = do_lock(project, strategy, tracked_names, reqs)
    do_sync(project, sections=(section, ), default=False, clean=False)
    if unconstrained:
        # Need to update version constraints
        save_version_specifiers(updated_deps, resolved, save)
        project.add_dependencies(updated_deps)
        lockfile = project.lockfile
        project.write_lockfile(lockfile, False)
Exemple #20
0
 def __init__(self, requirement, parent):
     super().__init__("No version available for {}.".format(
         stream.green(requirement.as_line())))
     self.requirement = requirement
     self.parent = parent