This commit is contained in:
lz_db
2026-10-16 22:48:30 +08:00
parent 190de72571
commit 0b36fd3383
3161 changed files with 12787 additions and 77274 deletions
@@ -4,53 +4,28 @@ Provides the Distribution class, which represents the module distribution
being built/installed/distributed.
"""
from __future__ import annotations
import contextlib
import logging
import os
import pathlib
import re
import sys
import warnings
from collections.abc import Iterable, MutableMapping
import os
import re
import pathlib
import contextlib
from email import message_from_file
from typing import (
IO,
TYPE_CHECKING,
Any,
ClassVar,
Literal,
TypeVar,
Union,
overload,
)
from packaging.utils import canonicalize_name, canonicalize_version
try:
import warnings
except ImportError:
warnings = None
from ._log import log
from .debug import DEBUG
from .errors import (
from distutils.errors import (
DistutilsOptionError,
DistutilsModuleError,
DistutilsArgError,
DistutilsClassError,
DistutilsModuleError,
DistutilsOptionError,
)
from .fancy_getopt import FancyGetopt, translate_longopt
from .util import check_environ, rfc822_escape, strtobool
if TYPE_CHECKING:
from _typeshed import SupportsWrite
from typing_extensions import TypeAlias
# type-only import because of mutual dependence between these modules
from .cmd import Command
_CommandT = TypeVar("_CommandT", bound="Command")
_OptionsList: TypeAlias = list[
Union[tuple[str, Union[str, None], str, int], tuple[str, Union[str, None], str]]
]
from distutils.fancy_getopt import FancyGetopt, translate_longopt
from distutils.util import check_environ, strtobool, rfc822_escape
from distutils import log
from distutils.debug import DEBUG
# Regex to define acceptable Distutils command names. This is not *quite*
# the same as a Python NAME -- I don't allow leading underscores. The fact
@@ -59,7 +34,7 @@ _OptionsList: TypeAlias = list[
command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
def _ensure_list(value: str | Iterable[str], fieldname) -> str | list[str]:
def _ensure_list(value, fieldname):
if isinstance(value, str):
# a string containing comma separated values is okay. It will
# be converted to a list by Distribution.finalize_options().
@@ -69,7 +44,7 @@ def _ensure_list(value: str | Iterable[str], fieldname) -> str | list[str]:
typename = type(value).__name__
msg = "Warning: '{fieldname}' should be a list, got type '{typename}'"
msg = msg.format(**locals())
log.warning(msg)
log.log(log.WARN, msg)
value = list(value)
return value
@@ -96,7 +71,7 @@ class Distribution:
# don't want to pollute the commands with too many options that they
# have minimal control over.
# The fourth entry for verbose means that it can be repeated.
global_options: ClassVar[_OptionsList] = [
global_options = [
('verbose', 'v', "run verbosely (default)", 1),
('quiet', 'q', "run quietly (turns verbosity off)"),
('dry-run', 'n', "don't actually do anything"),
@@ -106,7 +81,7 @@ class Distribution:
# 'common_usage' is a short (2-3 line) string describing the common
# usage of the setup script.
common_usage: ClassVar[str] = """\
common_usage = """\
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
@@ -114,7 +89,7 @@ Common commands: (see '--help-commands' for more)
"""
# options that are not propagated to the commands
display_options: ClassVar[_OptionsList] = [
display_options = [
('help-commands', None, "list all available commands"),
('name', None, "print package name"),
('version', 'V', "print package version"),
@@ -141,17 +116,14 @@ Common commands: (see '--help-commands' for more)
('requires', None, "print the list of packages/modules required"),
('obsoletes', None, "print the list of packages/modules made obsolete"),
]
display_option_names: ClassVar[list[str]] = [
translate_longopt(x[0]) for x in display_options
]
display_option_names = [translate_longopt(x[0]) for x in display_options]
# negative options are options that exclude other options
negative_opt: ClassVar[dict[str, str]] = {'quiet': 'verbose'}
negative_opt = {'quiet': 'verbose'}
# -- Creation/initialization methods -------------------------------
# Can't Unpack a TypedDict with optional properties, so using Any instead
def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None: # noqa: C901
def __init__(self, attrs=None): # noqa: C901
"""Construct a new Distribution instance: initialize all the
attributes of a Distribution, and then use 'attrs' (a dictionary
mapping attribute names to values) to assign some of those
@@ -163,11 +135,11 @@ Common commands: (see '--help-commands' for more)
"""
# Default values for our command-line options
self.verbose = True
self.dry_run = False
self.help = False
self.verbose = 1
self.dry_run = 0
self.help = 0
for attr in self.display_option_names:
setattr(self, attr, False)
setattr(self, attr, 0)
# Store the distribution meta-data (name, version, author, and so
# forth) in a separate object -- we're getting to have enough
@@ -183,7 +155,7 @@ Common commands: (see '--help-commands' for more)
# can 1) quickly figure out which class to instantiate when
# we need to create a new command object, and 2) have a way
# for the setup script to override command classes
self.cmdclass: dict[str, type[Command]] = {}
self.cmdclass = {}
# 'command_packages' is a list of packages in which commands
# are searched for. The factory for command 'foo' is expected
@@ -191,20 +163,20 @@ Common commands: (see '--help-commands' for more)
# named here. This list is searched from the left; an error
# is raised if no named package provides the command being
# searched for. (Always access using get_command_packages().)
self.command_packages: str | list[str] | None = None
self.command_packages = None
# 'script_name' and 'script_args' are usually set to sys.argv[0]
# and sys.argv[1:], but they can be overridden when the caller is
# not necessarily a setup script run from the command-line.
self.script_name: str | os.PathLike[str] | None = None
self.script_args: list[str] | None = None
self.script_name = None
self.script_args = None
# 'command_options' is where we store command options between
# parsing them (from config files, the command-line, etc.) and when
# they are actually needed -- ie. when the command in question is
# instantiated. It is a dictionary of dictionaries of 2-tuples:
# command_options = { command_name : { option : (source, value) } }
self.command_options: dict[str, dict[str, tuple[str, str]]] = {}
self.command_options = {}
# 'dist_files' is the list of (command, pyversion, file) that
# have been created by any dist commands run so far. This is
@@ -215,13 +187,13 @@ Common commands: (see '--help-commands' for more)
# file. pyversion should not be used to specify minimum or
# maximum required Python versions; use the metainfo for that
# instead.
self.dist_files: list[tuple[str, str, str]] = []
self.dist_files = []
# These options are really the business of various commands, rather
# than of the Distribution itself. We provide aliases for them in
# Distribution as a convenience to the developer.
self.packages = None
self.package_data: dict[str, list[str]] = {}
self.package_data = {}
self.package_dir = None
self.py_modules = None
self.libraries = None
@@ -238,7 +210,7 @@ Common commands: (see '--help-commands' for more)
# the caller at all. 'command_obj' maps command names to
# Command instances -- that's how we enforce that every command
# class is a singleton.
self.command_obj: dict[str, Command] = {}
self.command_obj = {}
# 'have_run' maps command names to boolean values; it keeps track
# of whether we have actually run a particular command, to make it
@@ -250,7 +222,7 @@ Common commands: (see '--help-commands' for more)
# command object is created, and replaced with a true value when
# the command is successfully run. Thus it's probably best to use
# '.get()' rather than a straight lookup.
self.have_run: dict[str, bool] = {}
self.have_run = {}
# Now we'll use the attrs dictionary (ultimately, keyword args from
# the setup script) to possibly override any or all of these
@@ -264,20 +236,23 @@ Common commands: (see '--help-commands' for more)
options = attrs.get('options')
if options is not None:
del attrs['options']
for command, cmd_options in options.items():
for (command, cmd_options) in options.items():
opt_dict = self.get_option_dict(command)
for opt, val in cmd_options.items():
for (opt, val) in cmd_options.items():
opt_dict[opt] = ("setup script", val)
if 'licence' in attrs:
attrs['license'] = attrs['licence']
del attrs['licence']
msg = "'licence' distribution option is deprecated; use 'license'"
warnings.warn(msg)
if warnings is not None:
warnings.warn(msg)
else:
sys.stderr.write(msg + "\n")
# Now work on the rest of the attributes. Any attribute that's
# not already defined is invalid!
for key, val in attrs.items():
for (key, val) in attrs.items():
if hasattr(self.metadata, "set_" + key):
getattr(self.metadata, "set_" + key)(val)
elif hasattr(self.metadata, key):
@@ -285,7 +260,7 @@ Common commands: (see '--help-commands' for more)
elif hasattr(self, key):
setattr(self, key, val)
else:
msg = f"Unknown distribution option: {key!r}"
msg = "Unknown distribution option: %s" % repr(key)
warnings.warn(msg)
# no-user-cfg is handled before other command line args
@@ -297,8 +272,6 @@ Common commands: (see '--help-commands' for more)
self.want_user_cfg = True
if self.script_args is not None:
# Coerce any possible iterable from attrs into a list
self.script_args = list(self.script_args)
for arg in self.script_args:
if not arg.startswith('-'):
break
@@ -319,7 +292,7 @@ Common commands: (see '--help-commands' for more)
dict = self.command_options[command] = {}
return dict
def dump_option_dicts(self, header=None, commands=None, indent: str = "") -> None:
def dump_option_dicts(self, header=None, commands=None, indent=""):
from pprint import pformat
if commands is None: # dump all command option dicts
@@ -336,9 +309,9 @@ Common commands: (see '--help-commands' for more)
for cmd_name in commands:
opt_dict = self.command_options.get(cmd_name)
if opt_dict is None:
self.announce(indent + f"no option dict for '{cmd_name}' command")
self.announce(indent + "no option dict for '%s' command" % cmd_name)
else:
self.announce(indent + f"option dict for '{cmd_name}' command:")
self.announce(indent + "option dict for '%s' command:" % cmd_name)
out = pformat(opt_dict)
for line in out.split('\n'):
self.announce(indent + " " + line)
@@ -364,7 +337,7 @@ Common commands: (see '--help-commands' for more)
files = [str(path) for path in self._gen_paths() if os.path.isfile(path)]
if DEBUG:
self.announce("using config files: {}".format(', '.join(files)))
self.announce("using config files: %s" % ', '.join(files))
return files
@@ -377,8 +350,7 @@ Common commands: (see '--help-commands' for more)
prefix = '.' * (os.name == 'posix')
filename = prefix + 'pydistutils.cfg'
if self.want_user_cfg:
with contextlib.suppress(RuntimeError):
yield pathlib.Path('~').expanduser() / filename
yield pathlib.Path('~').expanduser() / filename
# All platforms support local setup.cfg
yield pathlib.Path('setup.cfg')
@@ -421,8 +393,8 @@ Common commands: (see '--help-commands' for more)
parser = ConfigParser()
for filename in filenames:
if DEBUG:
self.announce(f" reading {filename}")
parser.read(filename, encoding='utf-8')
self.announce(" reading %s" % filename)
parser.read(filename)
for section in parser.sections():
options = parser.options(section)
opt_dict = self.get_option_dict(section)
@@ -441,7 +413,7 @@ Common commands: (see '--help-commands' for more)
# to set Distribution options.
if 'global' in self.command_options:
for opt, (_src, val) in self.command_options['global'].items():
for (opt, (src, val)) in self.command_options['global'].items():
alias = self.negative_opt.get(opt)
try:
if alias:
@@ -493,7 +465,7 @@ Common commands: (see '--help-commands' for more)
parser.set_aliases({'licence': 'license'})
args = parser.getopt(args=self.script_args, object=self)
option_order = parser.get_option_order()
logging.getLogger().setLevel(logging.WARN - 10 * self.verbose)
log.set_verbosity(self.verbose)
# for display options we return immediately
if self.handle_display_options(option_order):
@@ -551,7 +523,7 @@ Common commands: (see '--help-commands' for more)
# Pull the current command from the head of the command line
command = args[0]
if not command_re.match(command):
raise SystemExit(f"invalid command name '{command}'")
raise SystemExit("invalid command name '%s'" % command)
self.commands.append(command)
# Dig up the command class that implements this command, so we
@@ -566,7 +538,7 @@ Common commands: (see '--help-commands' for more)
# to be sure that the basic "command" interface is implemented.
if not issubclass(cmd_class, Command):
raise DistutilsClassError(
f"command class {cmd_class} must subclass Command"
"command class %s must subclass Command" % cmd_class
)
# Also make sure that the command object provides a list of its
@@ -605,22 +577,23 @@ Common commands: (see '--help-commands' for more)
parser.set_negative_aliases(negative_opt)
(args, opts) = parser.getopt(args[1:])
if hasattr(opts, 'help') and opts.help:
self._show_help(parser, display_options=False, commands=[cmd_class])
self._show_help(parser, display_options=0, commands=[cmd_class])
return
if hasattr(cmd_class, 'help_options') and isinstance(
cmd_class.help_options, list
):
help_option_found = 0
for help_option, _short, _desc, func in cmd_class.help_options:
for (help_option, short, desc, func) in cmd_class.help_options:
if hasattr(opts, parser.get_attr_name(help_option)):
help_option_found = 1
if callable(func):
func()
else:
raise DistutilsClassError(
f"invalid help function {func!r} for help option '{help_option}': "
"invalid help function %r for help option '%s': "
"must be a callable object (function, etc.)"
% (func, help_option)
)
if help_option_found:
@@ -629,12 +602,12 @@ Common commands: (see '--help-commands' for more)
# Put the options from the command-line into their official
# holding pen, the 'command_options' dictionary.
opt_dict = self.get_option_dict(command)
for name, value in vars(opts).items():
for (name, value) in vars(opts).items():
opt_dict[name] = ("command line", value)
return args
def finalize_options(self) -> None:
def finalize_options(self):
"""Set final values for all the options on the Distribution
instance, analogous to the .finalize_options() method of Command
objects.
@@ -647,9 +620,7 @@ Common commands: (see '--help-commands' for more)
value = [elm.strip() for elm in value.split(',')]
setattr(self.metadata, attr, value)
def _show_help(
self, parser, global_options=True, display_options=True, commands: Iterable = ()
):
def _show_help(self, parser, global_options=1, display_options=1, commands=[]):
"""Show help for the setup script command-line in the form of
several lists of command-line options. 'parser' should be a
FancyGetopt instance; do not expect it to be returned in the
@@ -663,8 +634,8 @@ Common commands: (see '--help-commands' for more)
in 'commands'.
"""
# late import because of mutual dependence between these modules
from distutils.cmd import Command
from distutils.core import gen_usage
from distutils.cmd import Command
if global_options:
if display_options:
@@ -673,16 +644,17 @@ Common commands: (see '--help-commands' for more)
options = self.global_options
parser.set_option_table(options)
parser.print_help(self.common_usage + "\nGlobal options:")
print()
print('')
if display_options:
parser.set_option_table(self.display_options)
parser.print_help(
"Information display options (just display information, ignore any commands)"
"Information display options (just display "
+ "information, ignore any commands)"
)
print()
print('')
for command in commands:
for command in self.commands:
if isinstance(command, type) and issubclass(command, Command):
klass = command
else:
@@ -693,8 +665,8 @@ Common commands: (see '--help-commands' for more)
)
else:
parser.set_option_table(klass.user_options)
parser.print_help(f"Options for '{klass.__name__}' command:")
print()
parser.print_help("Options for '%s' command:" % klass.__name__)
print('')
print(gen_usage(self.script_name))
@@ -711,7 +683,7 @@ Common commands: (see '--help-commands' for more)
# we ignore "foo bar").
if self.help_commands:
self.print_commands()
print()
print('')
print(gen_usage(self.script_name))
return 1
@@ -719,15 +691,15 @@ Common commands: (see '--help-commands' for more)
# display that metadata in the order in which the user supplied the
# metadata options.
any_display_options = 0
is_display_option = set()
is_display_option = {}
for option in self.display_options:
is_display_option.add(option[0])
is_display_option[option[0]] = 1
for opt, val in option_order:
if val and opt in is_display_option:
for (opt, val) in option_order:
if val and is_display_option.get(opt):
opt = translate_longopt(opt)
value = getattr(self.metadata, "get_" + opt)()
if opt in ('keywords', 'platforms'):
if opt in ['keywords', 'platforms']:
print(','.join(value))
elif opt in ('classifiers', 'provides', 'requires', 'obsoletes'):
print('\n'.join(value))
@@ -737,7 +709,7 @@ Common commands: (see '--help-commands' for more)
return any_display_options
def print_command_list(self, commands, header, max_length) -> None:
def print_command_list(self, commands, header, max_length):
"""Print a subset of the list of all commands -- used by
'print_commands()'.
"""
@@ -752,9 +724,9 @@ Common commands: (see '--help-commands' for more)
except AttributeError:
description = "(no description available)"
print(f" {cmd:<{max_length}} {description}")
print(" %-*s %s" % (max_length, cmd, description))
def print_commands(self) -> None:
def print_commands(self):
"""Print out a help message listing all available commands with a
description of each. The list is divided into "standard commands"
(listed in distutils.command.__all__) and "extra commands"
@@ -765,9 +737,14 @@ Common commands: (see '--help-commands' for more)
import distutils.command
std_commands = distutils.command.__all__
is_std = set(std_commands)
is_std = {}
for cmd in std_commands:
is_std[cmd] = 1
extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std]
extra_commands = []
for cmd in self.cmdclass.keys():
if not is_std.get(cmd):
extra_commands.append(cmd)
max_length = 0
for cmd in std_commands + extra_commands:
@@ -791,9 +768,14 @@ Common commands: (see '--help-commands' for more)
import distutils.command
std_commands = distutils.command.__all__
is_std = set(std_commands)
is_std = {}
for cmd in std_commands:
is_std[cmd] = 1
extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std]
extra_commands = []
for cmd in self.cmdclass.keys():
if not is_std.get(cmd):
extra_commands.append(cmd)
rv = []
for cmd in std_commands + extra_commands:
@@ -821,7 +803,7 @@ Common commands: (see '--help-commands' for more)
self.command_packages = pkgs
return pkgs
def get_command_class(self, command: str) -> type[Command]:
def get_command_class(self, command):
"""Return the class that implements the Distutils command named by
'command'. First we check the 'cmdclass' dictionary; if the
command is mentioned there, we fetch the class object from the
@@ -838,7 +820,7 @@ Common commands: (see '--help-commands' for more)
return klass
for pkgname in self.get_command_packages():
module_name = f"{pkgname}.{command}"
module_name = "{}.{}".format(pkgname, command)
klass_name = command
try:
@@ -851,23 +833,16 @@ Common commands: (see '--help-commands' for more)
klass = getattr(module, klass_name)
except AttributeError:
raise DistutilsModuleError(
f"invalid command '{command}' (no class '{klass_name}' in module '{module_name}')"
"invalid command '%s' (no class '%s' in module '%s')"
% (command, klass_name, module_name)
)
self.cmdclass[command] = klass
return klass
raise DistutilsModuleError(f"invalid command '{command}'")
raise DistutilsModuleError("invalid command '%s'" % command)
@overload
def get_command_obj(
self, command: str, create: Literal[True] = True
) -> Command: ...
@overload
def get_command_obj(
self, command: str, create: Literal[False]
) -> Command | None: ...
def get_command_obj(self, command: str, create: bool = True) -> Command | None:
def get_command_obj(self, command, create=1):
"""Return the command object for 'command'. Normally this object
is cached on a previous call to 'get_command_obj()'; if no command
object for 'command' is in the cache, then we either create and
@@ -878,12 +853,12 @@ Common commands: (see '--help-commands' for more)
if DEBUG:
self.announce(
"Distribution.get_command_obj(): "
f"creating '{command}' command object"
"creating '%s' command object" % command
)
klass = self.get_command_class(command)
cmd_obj = self.command_obj[command] = klass(self)
self.have_run[command] = False
self.have_run[command] = 0
# Set any options that were supplied in config files
# or on the command line. (NB. support for error
@@ -910,10 +885,10 @@ Common commands: (see '--help-commands' for more)
option_dict = self.get_option_dict(command_name)
if DEBUG:
self.announce(f" setting options for '{command_name}' command:")
for option, (source, value) in option_dict.items():
self.announce(" setting options for '%s' command:" % command_name)
for (option, (source, value)) in option_dict.items():
if DEBUG:
self.announce(f" {option} = {value} (from {source})")
self.announce(" {} = {} (from {})".format(option, value, source))
try:
bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
except AttributeError:
@@ -933,22 +908,13 @@ Common commands: (see '--help-commands' for more)
setattr(command_obj, option, value)
else:
raise DistutilsOptionError(
f"error in {source}: command '{command_name}' has no such option '{option}'"
"error in %s: command '%s' has no such option '%s'"
% (source, command_name, option)
)
except ValueError as msg:
raise DistutilsOptionError(msg)
@overload
def reinitialize_command(
self, command: str, reinit_subcommands: bool = False
) -> Command: ...
@overload
def reinitialize_command(
self, command: _CommandT, reinit_subcommands: bool = False
) -> _CommandT: ...
def reinitialize_command(
self, command: str | Command, reinit_subcommands=False
) -> Command:
def reinitialize_command(self, command, reinit_subcommands=0):
"""Reinitializes a command to the state it was in when first
returned by 'get_command_obj()': ie., initialized but not yet
finalized. This provides the opportunity to sneak option
@@ -978,8 +944,8 @@ Common commands: (see '--help-commands' for more)
if not command.finalized:
return command
command.initialize_options()
command.finalized = False
self.have_run[command_name] = False
command.finalized = 0
self.have_run[command_name] = 0
self._set_command_options(command)
if reinit_subcommands:
@@ -990,10 +956,10 @@ Common commands: (see '--help-commands' for more)
# -- Methods that operate on the Distribution ----------------------
def announce(self, msg, level: int = logging.INFO) -> None:
def announce(self, msg, level=log.INFO):
log.log(level, msg)
def run_commands(self) -> None:
def run_commands(self):
"""Run each command that was seen on the setup script command line.
Uses the list of commands found and cache of command objects
created by 'get_command_obj()'.
@@ -1003,7 +969,7 @@ Common commands: (see '--help-commands' for more)
# -- Methods that operate on its Commands --------------------------
def run_command(self, command: str) -> None:
def run_command(self, command):
"""Do whatever it takes to run a command (including nothing at all,
if the command has already been run). Specifically: if we have
already created and run the command named by 'command', return
@@ -1019,32 +985,32 @@ Common commands: (see '--help-commands' for more)
cmd_obj = self.get_command_obj(command)
cmd_obj.ensure_finalized()
cmd_obj.run()
self.have_run[command] = True
self.have_run[command] = 1
# -- Distribution query methods ------------------------------------
def has_pure_modules(self) -> bool:
def has_pure_modules(self):
return len(self.packages or self.py_modules or []) > 0
def has_ext_modules(self) -> bool:
def has_ext_modules(self):
return self.ext_modules and len(self.ext_modules) > 0
def has_c_libraries(self) -> bool:
def has_c_libraries(self):
return self.libraries and len(self.libraries) > 0
def has_modules(self) -> bool:
def has_modules(self):
return self.has_pure_modules() or self.has_ext_modules()
def has_headers(self) -> bool:
def has_headers(self):
return self.headers and len(self.headers) > 0
def has_scripts(self) -> bool:
def has_scripts(self):
return self.scripts and len(self.scripts) > 0
def has_data_files(self) -> bool:
def has_data_files(self):
return self.data_files and len(self.data_files) > 0
def is_pure(self) -> bool:
def is_pure(self):
return (
self.has_pure_modules()
and not self.has_ext_modules()
@@ -1057,53 +1023,6 @@ Common commands: (see '--help-commands' for more)
# they are defined in a sneaky way: the constructor binds self.get_XXX
# to self.metadata.get_XXX. The actual code is in the
# DistributionMetadata class, below.
if TYPE_CHECKING:
# Unfortunately this means we need to specify them manually or not expose statically
def _(self) -> None:
self.get_name = self.metadata.get_name
self.get_version = self.metadata.get_version
self.get_fullname = self.metadata.get_fullname
self.get_author = self.metadata.get_author
self.get_author_email = self.metadata.get_author_email
self.get_maintainer = self.metadata.get_maintainer
self.get_maintainer_email = self.metadata.get_maintainer_email
self.get_contact = self.metadata.get_contact
self.get_contact_email = self.metadata.get_contact_email
self.get_url = self.metadata.get_url
self.get_license = self.metadata.get_license
self.get_licence = self.metadata.get_licence
self.get_description = self.metadata.get_description
self.get_long_description = self.metadata.get_long_description
self.get_keywords = self.metadata.get_keywords
self.get_platforms = self.metadata.get_platforms
self.get_classifiers = self.metadata.get_classifiers
self.get_download_url = self.metadata.get_download_url
self.get_requires = self.metadata.get_requires
self.get_provides = self.metadata.get_provides
self.get_obsoletes = self.metadata.get_obsoletes
# Default attributes generated in __init__ from self.display_option_names
help_commands: bool
name: str | Literal[False]
version: str | Literal[False]
fullname: str | Literal[False]
author: str | Literal[False]
author_email: str | Literal[False]
maintainer: str | Literal[False]
maintainer_email: str | Literal[False]
contact: str | Literal[False]
contact_email: str | Literal[False]
url: str | Literal[False]
license: str | Literal[False]
licence: str | Literal[False]
description: str | Literal[False]
long_description: str | Literal[False]
platforms: str | list[str] | Literal[False]
classifiers: str | list[str] | Literal[False]
keywords: str | list[str] | Literal[False]
provides: list[str] | Literal[False]
requires: list[str] | Literal[False]
obsoletes: list[str] | Literal[False]
class DistributionMetadata:
@@ -1135,40 +1054,37 @@ class DistributionMetadata:
"obsoletes",
)
def __init__(
self, path: str | bytes | os.PathLike[str] | os.PathLike[bytes] | None = None
) -> None:
def __init__(self, path=None):
if path is not None:
self.read_pkg_file(open(path))
else:
self.name: str | None = None
self.version: str | None = None
self.author: str | None = None
self.author_email: str | None = None
self.maintainer: str | None = None
self.maintainer_email: str | None = None
self.url: str | None = None
self.license: str | None = None
self.description: str | None = None
self.long_description: str | None = None
self.keywords: str | list[str] | None = None
self.platforms: str | list[str] | None = None
self.classifiers: str | list[str] | None = None
self.download_url: str | None = None
self.name = None
self.version = None
self.author = None
self.author_email = None
self.maintainer = None
self.maintainer_email = None
self.url = None
self.license = None
self.description = None
self.long_description = None
self.keywords = None
self.platforms = None
self.classifiers = None
self.download_url = None
# PEP 314
self.provides: str | list[str] | None = None
self.requires: str | list[str] | None = None
self.obsoletes: str | list[str] | None = None
self.provides = None
self.requires = None
self.obsoletes = None
def read_pkg_file(self, file: IO[str]) -> None:
def read_pkg_file(self, file):
"""Reads the metadata values from a file object."""
msg = message_from_file(file)
def _read_field(name: str) -> str | None:
def _read_field(name):
value = msg[name]
if value and value != "UNKNOWN":
return value
return None
def _read_list(name):
values = msg.get_all(name, None)
@@ -1212,14 +1128,14 @@ class DistributionMetadata:
self.provides = None
self.obsoletes = None
def write_pkg_info(self, base_dir: str | os.PathLike[str]) -> None:
def write_pkg_info(self, base_dir):
"""Write the PKG-INFO file into the release tree."""
with open(
os.path.join(base_dir, 'PKG-INFO'), 'w', encoding='UTF-8'
) as pkg_info:
self.write_pkg_file(pkg_info)
def write_pkg_file(self, file: SupportsWrite[str]) -> None:
def write_pkg_file(self, file):
"""Write the PKG-INFO format data to a file object."""
version = '1.0'
if (
@@ -1232,9 +1148,9 @@ class DistributionMetadata:
version = '1.1'
# required fields
file.write(f'Metadata-Version: {version}\n')
file.write(f'Name: {self.get_name()}\n')
file.write(f'Version: {self.get_version()}\n')
file.write('Metadata-Version: %s\n' % version)
file.write('Name: %s\n' % self.get_name())
file.write('Version: %s\n' % self.get_version())
def maybe_write(header, val):
if val:
@@ -1261,106 +1177,87 @@ class DistributionMetadata:
def _write_list(self, file, name, values):
values = values or []
for value in values:
file.write(f'{name}: {value}\n')
file.write('{}: {}\n'.format(name, value))
# -- Metadata query methods ----------------------------------------
def get_name(self) -> str:
def get_name(self):
return self.name or "UNKNOWN"
def get_version(self) -> str:
def get_version(self):
return self.version or "0.0.0"
def get_fullname(self) -> str:
return self._fullname(self.get_name(), self.get_version())
def get_fullname(self):
return "{}-{}".format(self.get_name(), self.get_version())
@staticmethod
def _fullname(name: str, version: str) -> str:
"""
>>> DistributionMetadata._fullname('setup.tools', '1.0-2')
'setup_tools-1.0.post2'
>>> DistributionMetadata._fullname('setup-tools', '1.2post2')
'setup_tools-1.2.post2'
>>> DistributionMetadata._fullname('setup-tools', '1.0-r2')
'setup_tools-1.0.post2'
>>> DistributionMetadata._fullname('setup.tools', '1.0.post')
'setup_tools-1.0.post0'
>>> DistributionMetadata._fullname('setup.tools', '1.0+ubuntu-1')
'setup_tools-1.0+ubuntu.1'
"""
return "{}-{}".format(
canonicalize_name(name).replace('-', '_'),
canonicalize_version(version, strip_trailing_zero=False),
)
def get_author(self) -> str | None:
def get_author(self):
return self.author
def get_author_email(self) -> str | None:
def get_author_email(self):
return self.author_email
def get_maintainer(self) -> str | None:
def get_maintainer(self):
return self.maintainer
def get_maintainer_email(self) -> str | None:
def get_maintainer_email(self):
return self.maintainer_email
def get_contact(self) -> str | None:
def get_contact(self):
return self.maintainer or self.author
def get_contact_email(self) -> str | None:
def get_contact_email(self):
return self.maintainer_email or self.author_email
def get_url(self) -> str | None:
def get_url(self):
return self.url
def get_license(self) -> str | None:
def get_license(self):
return self.license
get_licence = get_license
def get_description(self) -> str | None:
def get_description(self):
return self.description
def get_long_description(self) -> str | None:
def get_long_description(self):
return self.long_description
def get_keywords(self) -> str | list[str]:
def get_keywords(self):
return self.keywords or []
def set_keywords(self, value: str | Iterable[str]) -> None:
def set_keywords(self, value):
self.keywords = _ensure_list(value, 'keywords')
def get_platforms(self) -> str | list[str] | None:
def get_platforms(self):
return self.platforms
def set_platforms(self, value: str | Iterable[str]) -> None:
def set_platforms(self, value):
self.platforms = _ensure_list(value, 'platforms')
def get_classifiers(self) -> str | list[str]:
def get_classifiers(self):
return self.classifiers or []
def set_classifiers(self, value: str | Iterable[str]) -> None:
def set_classifiers(self, value):
self.classifiers = _ensure_list(value, 'classifiers')
def get_download_url(self) -> str | None:
def get_download_url(self):
return self.download_url
# PEP 314
def get_requires(self) -> str | list[str]:
def get_requires(self):
return self.requires or []
def set_requires(self, value: Iterable[str]) -> None:
def set_requires(self, value):
import distutils.versionpredicate
for v in value:
distutils.versionpredicate.VersionPredicate(v)
self.requires = list(value)
def get_provides(self) -> str | list[str]:
def get_provides(self):
return self.provides or []
def set_provides(self, value: Iterable[str]) -> None:
def set_provides(self, value):
value = [v.strip() for v in value]
for v in value:
import distutils.versionpredicate
@@ -1368,10 +1265,10 @@ class DistributionMetadata:
distutils.versionpredicate.split_provision(v)
self.provides = value
def get_obsoletes(self) -> str | list[str]:
def get_obsoletes(self):
return self.obsoletes or []
def set_obsoletes(self, value: Iterable[str]) -> None:
def set_obsoletes(self, value):
import distutils.versionpredicate
for v in value:
@@ -1383,4 +1280,7 @@ def fix_help_options(options):
"""Convert a 4-tuple 'help_options' list as found in various command
classes to the 3-tuple form required by FancyGetopt.
"""
return [opt[0:3] for opt in options]
new_options = []
for help_tuple in options:
new_options.append(help_tuple[0:3])
return new_options