Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/commands/search.py
4804 views
import logging1import shutil2import sys3import textwrap4import xmlrpc.client5from collections import OrderedDict6from optparse import Values7from typing import TYPE_CHECKING, Dict, List, Optional89from pip._vendor.packaging.version import parse as parse_version1011from pip._internal.cli.base_command import Command12from pip._internal.cli.req_command import SessionCommandMixin13from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS14from pip._internal.exceptions import CommandError15from pip._internal.metadata import get_default_environment16from pip._internal.models.index import PyPI17from pip._internal.network.xmlrpc import PipXmlrpcTransport18from pip._internal.utils.logging import indent_log19from pip._internal.utils.misc import write_output2021if TYPE_CHECKING:22from typing import TypedDict2324class TransformedHit(TypedDict):25name: str26summary: str27versions: List[str]282930logger = logging.getLogger(__name__)313233class SearchCommand(Command, SessionCommandMixin):34"""Search for PyPI packages whose name or summary contains <query>."""3536usage = """37%prog [options] <query>"""38ignore_require_venv = True3940def add_options(self) -> None:41self.cmd_opts.add_option(42"-i",43"--index",44dest="index",45metavar="URL",46default=PyPI.pypi_url,47help="Base URL of Python Package Index (default %default)",48)4950self.parser.insert_option_group(0, self.cmd_opts)5152def run(self, options: Values, args: List[str]) -> int:53if not args:54raise CommandError("Missing required argument (search query).")55query = args56pypi_hits = self.search(query, options)57hits = transform_hits(pypi_hits)5859terminal_width = None60if sys.stdout.isatty():61terminal_width = shutil.get_terminal_size()[0]6263print_results(hits, terminal_width=terminal_width)64if pypi_hits:65return SUCCESS66return NO_MATCHES_FOUND6768def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:69index_url = options.index7071session = self.get_default_session(options)7273transport = PipXmlrpcTransport(index_url, session)74pypi = xmlrpc.client.ServerProxy(index_url, transport)75try:76hits = pypi.search({"name": query, "summary": query}, "or")77except xmlrpc.client.Fault as fault:78message = "XMLRPC request failed [code: {code}]\n{string}".format(79code=fault.faultCode,80string=fault.faultString,81)82raise CommandError(message)83assert isinstance(hits, list)84return hits858687def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:88"""89The list from pypi is really a list of versions. We want a list of90packages with the list of versions stored inline. This converts the91list from pypi into one we can use.92"""93packages: Dict[str, "TransformedHit"] = OrderedDict()94for hit in hits:95name = hit["name"]96summary = hit["summary"]97version = hit["version"]9899if name not in packages.keys():100packages[name] = {101"name": name,102"summary": summary,103"versions": [version],104}105else:106packages[name]["versions"].append(version)107108# if this is the highest version, replace summary and score109if version == highest_version(packages[name]["versions"]):110packages[name]["summary"] = summary111112return list(packages.values())113114115def print_dist_installation_info(name: str, latest: str) -> None:116env = get_default_environment()117dist = env.get_distribution(name)118if dist is not None:119with indent_log():120if dist.version == latest:121write_output("INSTALLED: %s (latest)", dist.version)122else:123write_output("INSTALLED: %s", dist.version)124if parse_version(latest).pre:125write_output(126"LATEST: %s (pre-release; install"127" with `pip install --pre`)",128latest,129)130else:131write_output("LATEST: %s", latest)132133134def print_results(135hits: List["TransformedHit"],136name_column_width: Optional[int] = None,137terminal_width: Optional[int] = None,138) -> None:139if not hits:140return141if name_column_width is None:142name_column_width = (143max(144[145len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))146for hit in hits147]148)149+ 4150)151152for hit in hits:153name = hit["name"]154summary = hit["summary"] or ""155latest = highest_version(hit.get("versions", ["-"]))156if terminal_width is not None:157target_width = terminal_width - name_column_width - 5158if target_width > 10:159# wrap and indent summary to fit terminal160summary_lines = textwrap.wrap(summary, target_width)161summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)162163name_latest = f"{name} ({latest})"164line = f"{name_latest:{name_column_width}} - {summary}"165try:166write_output(line)167print_dist_installation_info(name, latest)168except UnicodeEncodeError:169pass170171172def highest_version(versions: List[str]) -> str:173return max(versions, key=parse_version)174175176