2 # Copyright (C) The Arvados Authors. All rights reserved.
4 # SPDX-License-Identifier: AGPL-3.0
5 """pypkg_info.py - Introspect installed Python packages
7 This tool can read metadata about any Python package installed in the current
8 environment and report it out in various formats. We use this mainly to pass
9 information through when building distribution packages.
14 import importlib.metadata
18 from pathlib import PurePath
21 def format_metadata(self, key, value):
24 def format_path(self, path):
28 class FPMFormat(RawFormat):
29 PYTHON_METADATA_MAP = {
30 'summary': 'description',
33 def format_metadata(self, key, value):
35 key = self.PYTHON_METADATA_MAP.get(key, key)
36 return f'--{key}={value}'
39 class Formats(enum.Enum):
44 def from_arg(cls, arg):
46 return cls[arg.upper()]
48 raise ValueError(f"unknown format {arg!r}") from None
51 def report_binfiles(args):
53 PurePath('bin', path.name)
54 for pkg_name in args.package_names
55 for path in importlib.metadata.distribution(pkg_name).files
56 if path.parts[-3:-1] == ('..', 'bin')
58 fmt = args.format.value().format_path
59 return (fmt(path) for path in bin_names)
61 def report_metadata(args):
62 dist = importlib.metadata.distribution(args.package_name)
63 fmt = args.format.value().format_metadata
64 for key in args.metadata_key:
65 yield fmt(key, dist.metadata.get(key, ''))
67 def unescape_str(arg):
68 arg = arg.replace('\'', '\\\'')
69 return eval(f"'''{arg}'''", {})
71 def parse_arguments(arglist=None):
72 parser = argparse.ArgumentParser()
73 parser.set_defaults(action=None)
74 format_names = ', '.join(fmt.name.lower() for fmt in Formats)
77 choices=list(Formats),
79 type=Formats.from_arg,
80 help=f"Output format. Choices are: {format_names}",
86 help="Line ending. Python backslash escapes are supported. Default newline.",
88 subparsers = parser.add_subparsers()
90 binfiles = subparsers.add_parser('binfiles')
91 binfiles.set_defaults(action=report_binfiles)
92 binfiles.add_argument(
94 nargs=argparse.ONE_OR_MORE,
97 metadata = subparsers.add_parser('metadata')
98 metadata.set_defaults(action=report_metadata)
99 metadata.add_argument(
102 metadata.add_argument(
104 nargs=argparse.ONE_OR_MORE,
107 args = parser.parse_args()
108 if args.action is None:
109 parser.error("subcommand is required")
112 def main(arglist=None):
113 args = parse_arguments(arglist)
115 for line in args.action(args):
116 print(line, end=args.delimiter)
117 except importlib.metadata.PackageNotFoundError as error:
118 print(f"error: package not found: {error.args[0]}", file=sys.stderr)
119 return os.EX_NOTFOUND
123 if __name__ == '__main__':