Esempio n. 1
0
    def _list_config(self, project: Project, options: argparse.Namespace) -> None:
        stream.echo(
            "Home configuration ({}):".format(project.global_config._config_file)
        )
        with stream.indent("  "):
            for key in sorted(project.global_config):
                stream.echo(
                    stream.yellow(
                        "# " + project.global_config._config_map[key].description
                    ),
                    verbosity=stream.DETAIL,
                )
                stream.echo(f"{stream.cyan(key)} = {project.global_config[key]}")

        stream.echo()
        stream.echo(
            "Project configuration ({}):".format(project.project_config._config_file)
        )
        with stream.indent("  "):
            for key in sorted(project.project_config):
                stream.echo(
                    stream.yellow(
                        "# " + project.project_config._config_map[key].description
                    ),
                    verbosity=stream.DETAIL,
                )
                stream.echo(f"{stream.cyan(key)} = {project.project_config[key]}")
Esempio n. 2
0
    def handle(self, project: Project, options: argparse.Namespace) -> None:

        package = options.package
        req = parse_requirement(package)
        repository = project.get_repository()
        # reverse the result so that latest is at first.
        matches = repository.find_candidates(
            req, project.environment.python_requires, True)
        latest = next(iter(matches), None)
        if not latest:
            stream.echo(
                stream.yellow(f"No match found for the package {package!r}"),
                err=True)
            return
        latest_stable = next(filter(filter_stable, matches), None)
        installed = project.environment.get_working_set().get(package)

        metadata = latest.get_metadata()
        if metadata._legacy:
            result = ProjectInfo(dict(metadata._legacy.items()), True)
        else:
            result = ProjectInfo(dict(metadata._data), False)
        if latest_stable:
            result.latest_stable_version = str(latest_stable.version)
        if installed:
            result.installed_version = str(installed.version)

        stream.display_columns(list(result.generate_rows()))
Esempio n. 3
0
def ask_for_import(project: Project) -> None:
    """Show possible importable files and ask user to decide"""
    importable_files = list(find_importable_files(project))
    if not importable_files:
        return
    stream.echo(
        stream.cyan("Found following files from other formats that you may import:")
    )
    for i, (key, filepath) in enumerate(importable_files):
        stream.echo(f"{i}. {stream.green(filepath.as_posix())} ({key})")
    stream.echo(
        "{}. {}".format(
            len(importable_files),
            stream.yellow("don't do anything, I will import later."),
        )
    )
    choice = click.prompt(
        "Please select:",
        type=click.Choice([str(i) for i in range(len(importable_files) + 1)]),
        show_default=False,
    )
    if int(choice) == len(importable_files):
        return
    key, filepath = importable_files[int(choice)]
    do_import(project, filepath, key)
Esempio n. 4
0
def format_package(
    graph: DirectedGraph,
    package: Package,
    required: str = "",
    prefix: str = "",
    visited=None,
) -> str:
    """Format one package.

    :param graph: the dependency graph
    :param package: the package instance
    :param required: the version required by its parent
    :param prefix: prefix text for children
    :param visited: the visited package collection
    """
    if visited is None:
        visited = set()
    result = []
    version = (
        stream.red("[ not installed ]")
        if not package.version
        else stream.red(package.version)
        if required
        and required != "Any"
        and not SpecifierSet(required).contains(package.version)
        else stream.yellow(package.version)
    )
    if package.name in visited:
        version = stream.red("[circular]")
    required = f"[ required: {required} ]" if required else ""
    result.append(f"{stream.green(package.name, bold=True)} {version} {required}\n")
    if package.name in visited:
        return "".join(result)
    visited.add(package.name)
    try:
        *children, last = sorted(graph.iter_children(package), key=lambda p: p.name)
    except ValueError:  # No children nodes
        pass
    else:
        for child in children:
            required = str(package.requirements[child.name].specifier or "Any")
            result.append(
                prefix
                + NON_LAST_CHILD
                + format_package(
                    graph, child, required, prefix + NON_LAST_PREFIX, visited.copy()
                )
            )
        required = str(package.requirements[last.name].specifier or "Any")
        result.append(
            prefix
            + LAST_CHILD
            + format_package(
                graph, last, required, prefix + LAST_PREFIX, visited.copy()
            )
        )
    return "".join(result)
Esempio n. 5
0
    def format_help(self):
        formatter = self._get_formatter()
        formatter.io = stream
        if getattr(self, "is_root", False):
            banner = (
                cfonts.render(
                    "PDM",
                    font="slick",
                    gradient=["bright_red", "bright_green"],
                    space=False,
                )
                + "\n"
            )
            formatter._add_item(lambda x: x, [banner])
            self._positionals.title = "Commands"
        self._optionals.title = "Options"
        # description
        formatter.add_text(self.description)

        # usage
        formatter.add_usage(
            self.usage,
            self._actions,
            self._mutually_exclusive_groups,
            prefix=stream.yellow("Usage", bold=True) + ": ",
        )

        # positionals, optionals and user-defined groups
        for action_group in self._action_groups:
            formatter.start_section(
                stream.yellow(action_group.title, bold=True)
                if action_group.title
                else None
            )
            formatter.add_text(action_group.description)
            formatter.add_arguments(action_group._group_actions)
            formatter.end_section()

        # epilog
        formatter.add_text(self.epilog)
        # determine help from format above
        return formatter.format_help()
Esempio n. 6
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,
    )
Esempio n. 7
0
 def __delitem__(self, key) -> None:
     self._data.pop(key, None)
     try:
         del self._file_data[key]
     except KeyError:
         pass
     else:
         env_var = self._config_map[key].env_var
         if env_var is not None and env_var in os.environ:
             stream.echo(
                 stream.yellow(
                     "WARNING: the config is shadowed by env var '{}', "
                     "set value won't take effect.".format(env_var)))
         self._save_config()
Esempio n. 8
0
    def __setitem__(self, key: str, value: Any) -> None:
        if key not in self._config_map:
            raise NoConfigError(key)
        if not self.is_global and self._config_map[key].global_only:
            raise ValueError(
                f"Config item '{key}' is not allowed to set in project config."
            )

        value = self._config_map[key].coerce(value)
        env_var = self._config_map[key].env_var
        if env_var is not None and env_var in os.environ:
            stream.echo(
                stream.yellow(
                    "WARNING: the config is shadowed by env var '{}', "
                    "the value set won't take effect.".format(env_var)))
        self._data[key] = value
        self._file_data[key] = value
        self._save_config()
Esempio n. 9
0
    def main(self, args=None, prog_name=None, obj=None, **extra):
        """The main entry function"""
        from pdm.models.pip_shims import global_tempdir_manager

        self.init_parser()
        self.load_plugins()

        self.parser.set_defaults(global_project=None)
        options = self.parser.parse_args(args or None)
        stream.set_verbosity(options.verbose)

        if obj is not None:
            options.project = obj
        if options.global_project:
            options.project = options.global_project
        if options.pep582:
            print_pep582_command(options.pep582)
            sys.exit(0)
        if not getattr(options, "project", None):
            options.project = self.project_class()

        # Add reverse reference for core object
        options.project.core = self
        migrate_pyproject(options.project)

        try:
            f = options.handler
        except AttributeError:
            self.parser.print_help()
            sys.exit(1)
        else:
            try:
                with global_tempdir_manager():
                    f(options.project, options)
            except Exception:
                etype, err, traceback = sys.exc_info()
                if stream.verbosity > stream.NORMAL:
                    raise err.with_traceback(traceback)
                stream.echo(f"{stream.red('[' + etype.__name__ + ']')}: {err}",
                            err=True)
                stream.echo(
                    stream.yellow("Add '-v' to see the detailed traceback"))
                sys.exit(1)
Esempio n. 10
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
Esempio n. 11
0
    def search(self, query: str) -> SearchResult:
        pypi_simple = self.sources[0]["url"].rstrip("/")
        results = []

        if pypi_simple.endswith("/simple"):
            search_url = pypi_simple[:-6] + "search"
        else:
            search_url = pypi_simple + "/search"

        with self.environment.get_finder() as finder:
            session = finder.session
            resp = session.get(search_url, params={"q": query})
            if resp.status_code == 404:
                stream.echo(
                    stream.yellow(
                        f"{pypi_simple!r} doesn't support '/search' endpoint, fallback "
                        f"to {self.DEFAULT_INDEX_URL!r} now.\n"
                        "This may take longer depending on your network condition."
                    ),
                    err=True,
                )
                resp = session.get(
                    f"{self.DEFAULT_INDEX_URL}/search", params={"q": query}
                )
            resp.raise_for_status()
            content = parse(resp.content, namespaceHTMLElements=False)

        for result in content.findall(".//*[@class='package-snippet']"):
            name = result.find("h3/*[@class='package-snippet__name']").text
            version = result.find("h3/*[@class='package-snippet__version']").text

            if not name or not version:
                continue

            description = result.find("p[@class='package-snippet__description']").text
            if not description:
                description = ""

            result = Package(name, version, description)
            results.append(result)

        return results
Esempio n. 12
0
def format_package(
    graph: DirectedGraph,
    package: Package,
    required: str = "",
    prefix: str = "",
    visited=None,
) -> str:
    """Format one package.

    :param graph: the dependency graph
    :param package: the package instance
    :param required: the version required by its parent
    :param prefix: prefix text for children
    :param visited: the visited package collection
    """
    if visited is None:
        visited = set()
    result = []
    version = (stream.red("[ not installed ]")
               if not package.version else stream.red(package.version)
               if required and required not in ("Any", "This project")
               and not SpecifierSet(required).contains(package.version) else
               stream.yellow(package.version))
    if package.name in visited:
        version = stream.red("[circular]")
    required = f"[ required: {required} ]" if required else "[ Not required ]"
    result.append(
        f"{stream.green(package.name, bold=True)} {version} {required}\n")
    if package.name in visited:
        return "".join(result)
    visited.add(package.name)
    children = sorted(graph.iter_children(package), key=lambda p: p.name)
    for i, child in enumerate(children):
        is_last = i == len(children) - 1
        head = LAST_CHILD if is_last else NON_LAST_CHILD
        cur_prefix = LAST_PREFIX if is_last else NON_LAST_PREFIX
        required = str(package.requirements[child.name].specifier or "Any")
        result.append(prefix + head +
                      format_package(graph, child, required, prefix +
                                     cur_prefix, visited.copy()))
    return "".join(result)
Esempio n. 13
0
 def __setitem__(self, key: str, value: Any) -> None:
     if key not in self._config_map:
         raise NoConfigError(key)
     if not self.is_global and self._config_map[key].global_only:
         raise ValueError(
             f"Config item '{key}' is not allowed to set in project config."
         )
     if isinstance(value, str):
         if value.lower() == "false":
             value = False
         elif value.lower() == "true":
             value = True
     env_var = self._config_map[key].env_var
     if env_var is not None and env_var in os.environ:
         stream.echo(
             stream.yellow(
                 "WARNING: the config is shadowed by env var '{}', "
                 "set value won't take effect.".format(env_var)))
     self._data[key] = value
     self._file_data[key] = value
     self._save_config()
Esempio n. 14
0
def format_reverse_package(
    graph: DirectedGraph,
    package: Package,
    child: Optional[Package] = None,
    requires: str = "",
    prefix: str = "",
    visited=None,
):
    """Format one package for output reverse dependency graph."""
    if visited is None:
        visited = set()
    result = []
    version = (stream.red("[ not installed ]")
               if not package.version else stream.yellow(package.version))
    if package.name in visited:
        version = stream.red("[circular]")
    requires = (f"[ requires: {stream.red(requires)} ]"
                if requires not in ("Any", "") and child and child.version
                and not SpecifierSet(requires).contains(child.version) else
                "" if not requires else f"[ requires: {requires} ]")
    result.append(
        f"{stream.green(package.name, bold=True)} {version} {requires}\n")
    if package.name in visited:
        return "".join(result)
    visited.add(package.name)
    parents = sorted(filter(None, graph.iter_parents(package)),
                     key=lambda p: p.name)
    for i, parent in enumerate(parents):
        is_last = i == len(parents) - 1
        head = LAST_CHILD if is_last else NON_LAST_CHILD
        cur_prefix = LAST_PREFIX if is_last else NON_LAST_PREFIX
        requires = str(parent.requirements[package.name].specifier or "Any")
        result.append(
            prefix + head +
            format_reverse_package(graph, parent, package, requires, prefix +
                                   cur_prefix, visited.copy()))
    return "".join(result)
Esempio n. 15
0
    def main(self, args=None, prog_name=None, obj=None, **extra):
        """The main entry function"""
        from pdm.models.pip_shims import global_tempdir_manager

        self.init_parser()
        self.load_plugins()

        options = self.parser.parse_args(args or None)
        stream.set_verbosity(options.verbose)
        if options.ignore_python:
            os.environ["PDM_IGNORE_SAVED_PYTHON"] = "1"

        if options.pep582:
            print_pep582_command(options.pep582)
            sys.exit(0)

        self.ensure_project(options, obj)

        try:
            f = options.handler
        except AttributeError:
            self.parser.print_help()
            sys.exit(1)
        else:
            try:
                with global_tempdir_manager():
                    f(options.project, options)
            except Exception:
                etype, err, traceback = sys.exc_info()
                if stream.verbosity > stream.NORMAL:
                    raise err.with_traceback(traceback)
                stream.echo(
                    f"{stream.red('[' + etype.__name__ + ']')}: {err}", err=True
                )
                stream.echo(stream.yellow("Add '-v' to see the detailed traceback"))
                sys.exit(1)
Esempio n. 16
0
    def synchronize(self, clean: bool = True, dry_run: bool = False) -> None:
        """Synchronize the working set with pinned candidates.

        :param clean: Whether to remove unneeded packages, defaults to True.
        :param dry_run: If set to True, only prints actions without actually do them.
        """
        to_add, to_update, to_remove = self.compare_with_working_set()
        if not clean:
            to_remove = []
        if not any([to_add, to_update, to_remove]):
            stream.echo(
                stream.yellow(
                    "All packages are synced to date, nothing to do."))
            if not dry_run:
                with stream.logging("install"):
                    self.update_project_egg_info()
            return
        to_do = {"remove": to_remove, "update": to_update, "add": to_add}
        self._show_headline(to_do)

        if dry_run:
            self._show_summary(to_do)
            return

        handlers = {
            "add": self.install_candidate,
            "update": self.update_candidate,
            "remove": self.remove_distribution,
        }

        sequential_jobs = []
        parallel_jobs = []
        # Self package will be installed after all other dependencies are installed.
        install_self = None
        for kind in to_do:
            for key in to_do[kind]:
                if (key == self.environment.project.meta.name and
                        self.environment.project.meta.project_name.lower()):
                    install_self = (kind, key)
                elif key in self.SEQUENTIAL_PACKAGES:
                    sequential_jobs.append((kind, key))
                elif key in self.candidates and self.candidates[
                        key].req.editable:
                    # Editable packages are installed sequentially.
                    sequential_jobs.append((kind, key))
                else:
                    parallel_jobs.append((kind, key))

        errors: List[str] = []
        failed_jobs: List[Tuple[str, str]] = []

        def update_progress(future, kind, key):
            if future.exception():
                failed_jobs.append((kind, key))
                error = future.exception()
                errors.extend([f"{kind} {stream.green(key)} failed:\n"] +
                              traceback.format_exception(
                                  type(error), error, error.__traceback__))

        with stream.logging("install"), self.environment.activate():
            with stream.indent("  "):
                for job in sequential_jobs:
                    kind, key = job
                    handlers[kind](key)
                for i in range(self.retry_times + 1):
                    with self.create_executor() as executor:
                        for job in parallel_jobs:
                            kind, key = job
                            future = executor.submit(handlers[kind], key)
                            future.add_done_callback(
                                functools.partial(update_progress,
                                                  kind=kind,
                                                  key=key))
                    if not failed_jobs or i == self.retry_times:
                        break
                    parallel_jobs, failed_jobs = failed_jobs, []
                    errors.clear()
                    stream.echo("Retry failed jobs")

            if errors:
                stream.echo(stream.red("\nERRORS:"))
                stream.echo("".join(errors), err=True)
                raise InstallationError(
                    "Some package operations are not complete yet")

            if install_self:
                stream.echo("Installing the project as an editable package...")
                with stream.indent("  "):
                    handlers[install_self[0]](install_self[1])
            else:
                self.update_project_egg_info()
            stream.echo(f"\n{CELE} All complete!")
Esempio n. 17
0
    def synchronize(self, clean: bool = True, dry_run: bool = False) -> None:
        """Synchronize the working set with pinned candidates.

        :param clean: Whether to remove unneeded packages, defaults to True.
        :param dry_run: If set to True, only prints actions without actually do them.
        """
        to_add, to_update, to_remove = self.compare_with_working_set()
        if not clean:
            to_remove = []
        lists_to_check = [to_add, to_update, to_remove]
        if not any(lists_to_check):
            if not dry_run:
                self.environment.write_site_py()
            stream.echo("All packages are synced to date, nothing to do.")
            return

        if dry_run:
            result = dict(
                add=[self.candidates[key] for key in to_add],
                update=[(self.working_set[key], self.candidates[key])
                        for key in to_update],
                remove=[self.working_set[key] for key in to_remove],
            )
            self.summarize(result, dry_run)
            return

        handlers = {
            "add": self.install_candidate,
            "update": self.update_candidate,
            "remove": self.remove_distribution,
        }

        result = defaultdict(list)
        failed = defaultdict(list)
        to_do = {"add": to_add, "update": to_update, "remove": to_remove}
        # Keep track of exceptions
        errors = []

        def update_progress(future, section, key, bar):
            if future.exception():
                failed[section].append(key)
                errors.append(future.exception())
            else:
                result[section].append(future.result())
            bar.update(1)

        with stream.logging("install"):
            with self.progressbar("Synchronizing:",
                                  sum(len(lst)
                                      for lst in to_do.values())) as (bar,
                                                                      pool):
                # First update packages, then remove and add
                for section in sorted(to_do, reverse=True):
                    # setup toolkits are installed sequentially before other packages.
                    for key in sorted(
                            to_do[section],
                            key=lambda x: x not in self.SEQUENTIAL_PACKAGES):
                        future = pool.submit(handlers[section], key)
                        future.add_done_callback(
                            functools.partial(update_progress,
                                              section=section,
                                              key=key,
                                              bar=bar))
                        if key in self.SEQUENTIAL_PACKAGES:
                            future.result()

            # Retry for failed items
            for i in range(self.RETRY_TIMES):
                if not any(failed.values()):
                    break
                stream.echo(
                    stream.yellow(
                        "\nSome packages failed to install, retrying..."))
                to_do = failed
                failed = defaultdict(list)
                errors.clear()
                with self.progressbar(
                        f"Retrying ({i + 1}/{self.RETRY_TIMES}):",
                        sum(len(lst) for lst in to_do.values()),
                ) as (bar, pool):

                    for section in sorted(to_do, reverse=True):
                        for key in sorted(
                                to_do[section],
                                key=lambda x: x not in self.
                                SEQUENTIAL_PACKAGES,
                        ):
                            future = pool.submit(handlers[section], key)
                            future.add_done_callback(
                                functools.partial(update_progress,
                                                  section=section,
                                                  key=key,
                                                  bar=bar))
                            if key in self.SEQUENTIAL_PACKAGES:
                                future.result()
            # End installation
            self.summarize(result)
            self.environment.write_site_py()
            if not any(failed.values()):
                return
            stream.echo("\n")
            error_msg = []
            if failed["add"] + failed["update"]:
                error_msg.append(
                    "Installation failed: "
                    f"{', '.join(failed['add'] + failed['update'])}")
            if failed["remove"]:
                error_msg.append(
                    f"Removal failed: {', '.join(failed['remove'])}")
            for error in errors:
                stream.echo(
                    "".join(
                        traceback.format_exception(type(error), error,
                                                   error.__traceback__)),
                    verbosity=stream.DEBUG,
                )
            raise InstallationError("\n" + "\n".join(error_msg))
Esempio n. 18
0
def format_dist(dist: Distribution) -> str:
    formatter = "{version}{path}"
    path = ""
    if is_dist_editable(dist):
        path = f" (-e {dist.location})"
    return formatter.format(version=stream.yellow(dist.version), path=path)