Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
dist.py973 linesDownload Raw Back to setuptools
1__all__ = ['Distribution']2 3 4import io5import itertools6import numbers7import os8import re9import sys10from contextlib import suppress11from glob import iglob12from pathlib import Path13from typing import TYPE_CHECKING, Dict, List, MutableMapping, Optional, Set, Tuple14 15import distutils.cmd16import distutils.command17import distutils.core18import distutils.dist19import distutils.log20from distutils.debug import DEBUG21from distutils.errors import DistutilsOptionError, DistutilsSetupError22from distutils.fancy_getopt import translate_longopt23from distutils.util import strtobool24 25from .extern.more_itertools import partition, unique_everseen26from .extern.ordered_set import OrderedSet27from .extern.packaging.markers import InvalidMarker, Marker28from .extern.packaging.specifiers import InvalidSpecifier, SpecifierSet29from .extern.packaging.version import Version30 31from . import _entry_points32from . import _normalization33from . import _reqs34from . import command as _  # noqa  -- imported for side-effects35from ._importlib import metadata36from .config import setupcfg, pyprojecttoml37from .discovery import ConfigDiscovery38from .monkey import get_unpatched39from .warnings import InformationOnly, SetuptoolsDeprecationWarning40 41 42sequence = tuple, list43 44 45def check_importable(dist, attr, value):46    try:47        ep = metadata.EntryPoint(value=value, name=None, group=None)48        assert not ep.extras49    except (TypeError, ValueError, AttributeError, AssertionError) as e:50        raise DistutilsSetupError(51            "%r must be importable 'module:attrs' string (got %r)" % (attr, value)52        ) from e53 54 55def assert_string_list(dist, attr, value):56    """Verify that value is a string list"""57    try:58        # verify that value is a list or tuple to exclude unordered59        # or single-use iterables60        assert isinstance(value, (list, tuple))61        # verify that elements of value are strings62        assert ''.join(value) != value63    except (TypeError, ValueError, AttributeError, AssertionError) as e:64        raise DistutilsSetupError(65            "%r must be a list of strings (got %r)" % (attr, value)66        ) from e67 68 69def check_nsp(dist, attr, value):70    """Verify that namespace packages are valid"""71    ns_packages = value72    assert_string_list(dist, attr, ns_packages)73    for nsp in ns_packages:74        if not dist.has_contents_for(nsp):75            raise DistutilsSetupError(76                "Distribution contains no modules or packages for "77                + "namespace package %r" % nsp78            )79        parent, sep, child = nsp.rpartition('.')80        if parent and parent not in ns_packages:81            distutils.log.warn(82                "WARNING: %r is declared as a package namespace, but %r"83                " is not: please correct this in setup.py",84                nsp,85                parent,86            )87        SetuptoolsDeprecationWarning.emit(88            "The namespace_packages parameter is deprecated.",89            "Please replace its usage with implicit namespaces (PEP 420).",90            see_docs="references/keywords.html#keyword-namespace-packages",91            # TODO: define due_date, it may break old packages that are no longer92            # maintained (e.g. sphinxcontrib extensions) when installed from source.93            # Warning officially introduced in May 2022, however the deprecation94            # was mentioned much earlier in the docs (May 2020, see #2149).95        )96 97 98def check_extras(dist, attr, value):99    """Verify that extras_require mapping is valid"""100    try:101        list(itertools.starmap(_check_extra, value.items()))102    except (TypeError, ValueError, AttributeError) as e:103        raise DistutilsSetupError(104            "'extras_require' must be a dictionary whose values are "105            "strings or lists of strings containing valid project/version "106            "requirement specifiers."107        ) from e108 109 110def _check_extra(extra, reqs):111    name, sep, marker = extra.partition(':')112    try:113        _check_marker(marker)114    except InvalidMarker:115        msg = f"Invalid environment marker: {marker} ({extra!r})"116        raise DistutilsSetupError(msg) from None117    list(_reqs.parse(reqs))118 119 120def _check_marker(marker):121    if not marker:122        return123    m = Marker(marker)124    m.evaluate()125 126 127def assert_bool(dist, attr, value):128    """Verify that value is True, False, 0, or 1"""129    if bool(value) != value:130        tmpl = "{attr!r} must be a boolean value (got {value!r})"131        raise DistutilsSetupError(tmpl.format(attr=attr, value=value))132 133 134def invalid_unless_false(dist, attr, value):135    if not value:136        DistDeprecationWarning.emit(f"{attr} is ignored.")137        # TODO: should there be a `due_date` here?138        return139    raise DistutilsSetupError(f"{attr} is invalid.")140 141 142def check_requirements(dist, attr, value):143    """Verify that install_requires is a valid requirements list"""144    try:145        list(_reqs.parse(value))146        if isinstance(value, (dict, set)):147            raise TypeError("Unordered types are not allowed")148    except (TypeError, ValueError) as error:149        tmpl = (150            "{attr!r} must be a string or list of strings "151            "containing valid project/version requirement specifiers; {error}"152        )153        raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error154 155 156def check_specifier(dist, attr, value):157    """Verify that value is a valid version specifier"""158    try:159        SpecifierSet(value)160    except (InvalidSpecifier, AttributeError) as error:161        tmpl = (162            "{attr!r} must be a string " "containing valid version specifiers; {error}"163        )164        raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error165 166 167def check_entry_points(dist, attr, value):168    """Verify that entry_points map is parseable"""169    try:170        _entry_points.load(value)171    except Exception as e:172        raise DistutilsSetupError(e) from e173 174 175def check_test_suite(dist, attr, value):176    if not isinstance(value, str):177        raise DistutilsSetupError("test_suite must be a string")178 179 180def check_package_data(dist, attr, value):181    """Verify that value is a dictionary of package names to glob lists"""182    if not isinstance(value, dict):183        raise DistutilsSetupError(184            "{!r} must be a dictionary mapping package names to lists of "185            "string wildcard patterns".format(attr)186        )187    for k, v in value.items():188        if not isinstance(k, str):189            raise DistutilsSetupError(190                "keys of {!r} dict must be strings (got {!r})".format(attr, k)191            )192        assert_string_list(dist, 'values of {!r} dict'.format(attr), v)193 194 195def check_packages(dist, attr, value):196    for pkgname in value:197        if not re.match(r'\w+(\.\w+)*', pkgname):198            distutils.log.warn(199                "WARNING: %r not a valid package name; please use only "200                ".-separated package names in setup.py",201                pkgname,202            )203 204 205if TYPE_CHECKING:206    # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962207    _Distribution = distutils.core.Distribution208else:209    _Distribution = get_unpatched(distutils.core.Distribution)210 211 212class Distribution(_Distribution):213    """Distribution with support for tests and package data214 215    This is an enhanced version of 'distutils.dist.Distribution' that216    effectively adds the following new optional keyword arguments to 'setup()':217 218     'install_requires' -- a string or sequence of strings specifying project219        versions that the distribution requires when installed, in the format220        used by 'pkg_resources.require()'.  They will be installed221        automatically when the package is installed.  If you wish to use222        packages that are not available in PyPI, or want to give your users an223        alternate download location, you can add a 'find_links' option to the224        '[easy_install]' section of your project's 'setup.cfg' file, and then225        setuptools will scan the listed web pages for links that satisfy the226        requirements.227 228     'extras_require' -- a dictionary mapping names of optional "extras" to the229        additional requirement(s) that using those extras incurs. For example,230        this::231 232            extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])233 234        indicates that the distribution can optionally provide an extra235        capability called "reST", but it can only be used if docutils and236        reSTedit are installed.  If the user installs your package using237        EasyInstall and requests one of your extras, the corresponding238        additional requirements will be installed if needed.239 240     'test_suite' -- the name of a test suite to run for the 'test' command.241        If the user runs 'python setup.py test', the package will be installed,242        and the named test suite will be run.  The format is the same as243        would be used on a 'unittest.py' command line.  That is, it is the244        dotted name of an object to import and call to generate a test suite.245 246     'package_data' -- a dictionary mapping package names to lists of filenames247        or globs to use to find data files contained in the named packages.248        If the dictionary has filenames or globs listed under '""' (the empty249        string), those names will be searched for in every package, in addition250        to any names for the specific package.  Data files found using these251        names/globs will be installed along with the package, in the same252        location as the package.  Note that globs are allowed to reference253        the contents of non-package subdirectories, as long as you use '/' as254        a path separator.  (Globs are automatically converted to255        platform-specific paths at runtime.)256 257    In addition to these new keywords, this class also has several new methods258    for manipulating the distribution's contents.  For example, the 'include()'259    and 'exclude()' methods can be thought of as in-place add and subtract260    commands that add or remove packages, modules, extensions, and so on from261    the distribution.262    """263 264    _DISTUTILS_UNSUPPORTED_METADATA = {265        'long_description_content_type': lambda: None,266        'project_urls': dict,267        'provides_extras': OrderedSet,268        'license_file': lambda: None,269        'license_files': lambda: None,270        'install_requires': list,271        'extras_require': dict,272    }273 274    _patched_dist = None275 276    def patch_missing_pkg_info(self, attrs):277        # Fake up a replacement for the data that would normally come from278        # PKG-INFO, but which might not yet be built if this is a fresh279        # checkout.280        #281        if not attrs or 'name' not in attrs or 'version' not in attrs:282            return283        name = _normalization.safe_name(str(attrs['name'])).lower()284        with suppress(metadata.PackageNotFoundError):285            dist = metadata.distribution(name)286            if dist is not None and not dist.read_text('PKG-INFO'):287                dist._version = _normalization.safe_version(str(attrs['version']))288                self._patched_dist = dist289 290    def __init__(self, attrs: Optional[MutableMapping] = None) -> None:291        have_package_data = hasattr(self, "package_data")292        if not have_package_data:293            self.package_data: Dict[str, List[str]] = {}294        attrs = attrs or {}295        self.dist_files: List[Tuple[str, str, str]] = []296        # Filter-out setuptools' specific options.297        self.src_root = attrs.pop("src_root", None)298        self.patch_missing_pkg_info(attrs)299        self.dependency_links = attrs.pop('dependency_links', [])300        self.setup_requires = attrs.pop('setup_requires', [])301        for ep in metadata.entry_points(group='distutils.setup_keywords'):302            vars(self).setdefault(ep.name, None)303 304        metadata_only = set(self._DISTUTILS_UNSUPPORTED_METADATA)305        metadata_only -= {"install_requires", "extras_require"}306        dist_attrs = {k: v for k, v in attrs.items() if k not in metadata_only}307        _Distribution.__init__(self, dist_attrs)308 309        # Private API (setuptools-use only, not restricted to Distribution)310        # Stores files that are referenced by the configuration and need to be in the311        # sdist (e.g. `version = file: VERSION.txt`)312        self._referenced_files: Set[str] = set()313 314        self.set_defaults = ConfigDiscovery(self)315 316        self._set_metadata_defaults(attrs)317 318        self.metadata.version = self._normalize_version(self.metadata.version)319        self._finalize_requires()320 321    def _validate_metadata(self):322        required = {"name"}323        provided = {324            key325            for key in vars(self.metadata)326            if getattr(self.metadata, key, None) is not None327        }328        missing = required - provided329 330        if missing:331            msg = f"Required package metadata is missing: {missing}"332            raise DistutilsSetupError(msg)333 334    def _set_metadata_defaults(self, attrs):335        """336        Fill-in missing metadata fields not supported by distutils.337        Some fields may have been set by other tools (e.g. pbr).338        Those fields (vars(self.metadata)) take precedence to339        supplied attrs.340        """341        for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():342            vars(self.metadata).setdefault(option, attrs.get(option, default()))343 344    @staticmethod345    def _normalize_version(version):346        from . import sic347 348        if isinstance(version, numbers.Number):349            # Some people apparently take "version number" too literally :)350            version = str(version)351        elif isinstance(version, sic) or version is None:352            return version353 354        normalized = str(Version(version))355        if version != normalized:356            InformationOnly.emit(f"Normalizing '{version}' to '{normalized}'")357            return normalized358        return version359 360    def _finalize_requires(self):361        """362        Set `metadata.python_requires` and fix environment markers363        in `install_requires` and `extras_require`.364        """365        if getattr(self, 'python_requires', None):366            self.metadata.python_requires = self.python_requires367 368        self._normalize_requires()369        self.metadata.install_requires = self.install_requires370        self.metadata.extras_require = self.extras_require371 372        if self.extras_require:373            for extra in self.extras_require.keys():374                # Setuptools allows a weird "<name>:<env markers> syntax for extras375                extra = extra.split(':')[0]376                if extra:377                    self.metadata.provides_extras.add(extra)378 379    def _normalize_requires(self):380        """Make sure requirement-related attributes exist and are normalized"""381        install_requires = getattr(self, "install_requires", None) or []382        extras_require = getattr(self, "extras_require", None) or {}383        self.install_requires = list(map(str, _reqs.parse(install_requires)))384        self.extras_require = {385            k: list(map(str, _reqs.parse(v or []))) for k, v in extras_require.items()386        }387 388    def _finalize_license_files(self) -> None:389        """Compute names of all license files which should be included."""390        license_files: Optional[List[str]] = self.metadata.license_files391        patterns: List[str] = license_files if license_files else []392 393        license_file: Optional[str] = self.metadata.license_file394        if license_file and license_file not in patterns:395            patterns.append(license_file)396 397        if license_files is None and license_file is None:398            # Default patterns match the ones wheel uses399            # See https://wheel.readthedocs.io/en/stable/user_guide.html400            # -> 'Including license files in the generated wheel file'401            patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']402 403        self.metadata.license_files = list(404            unique_everseen(self._expand_patterns(patterns))405        )406 407    @staticmethod408    def _expand_patterns(patterns):409        """410        >>> list(Distribution._expand_patterns(['LICENSE']))411        ['LICENSE']412        >>> list(Distribution._expand_patterns(['setup.cfg', 'LIC*']))413        ['setup.cfg', 'LICENSE']414        """415        return (416            path417            for pattern in patterns418            for path in sorted(iglob(pattern))419            if not path.endswith('~') and os.path.isfile(path)420        )421 422    # FIXME: 'Distribution._parse_config_files' is too complex (14)423    def _parse_config_files(self, filenames=None):  # noqa: C901424        """425        Adapted from distutils.dist.Distribution.parse_config_files,426        this method provides the same functionality in subtly-improved427        ways.428        """429        from configparser import ConfigParser430 431        # Ignore install directory options if we have a venv432        ignore_options = (433            []434            if sys.prefix == sys.base_prefix435            else [436                'install-base',437                'install-platbase',438                'install-lib',439                'install-platlib',440                'install-purelib',441                'install-headers',442                'install-scripts',443                'install-data',444                'prefix',445                'exec-prefix',446                'home',447                'user',448                'root',449            ]450        )451 452        ignore_options = frozenset(ignore_options)453 454        if filenames is None:455            filenames = self.find_config_files()456 457        if DEBUG:458            self.announce("Distribution.parse_config_files():")459 460        parser = ConfigParser()461        parser.optionxform = str462        for filename in filenames:463            with open(filename, encoding='utf-8') as reader:464                if DEBUG:465                    self.announce("  reading {filename}".format(**locals()))466                parser.read_file(reader)467            for section in parser.sections():468                options = parser.options(section)469                opt_dict = self.get_option_dict(section)470 471                for opt in options:472                    if opt == '__name__' or opt in ignore_options:473                        continue474 475                    val = parser.get(section, opt)476                    opt = self.warn_dash_deprecation(opt, section)477                    opt = self.make_option_lowercase(opt, section)478                    opt_dict[opt] = (filename, val)479 480            # Make the ConfigParser forget everything (so we retain481            # the original filenames that options come from)482            parser.__init__()483 484        if 'global' not in self.command_options:485            return486 487        # If there was a "global" section in the config file, use it488        # to set Distribution options.489 490        for opt, (src, val) in self.command_options['global'].items():491            alias = self.negative_opt.get(opt)492            if alias:493                val = not strtobool(val)494            elif opt in ('verbose', 'dry_run'):  # ugh!495                val = strtobool(val)496 497            try:498                setattr(self, alias or opt, val)499            except ValueError as e:500                raise DistutilsOptionError(e) from e501 502    def warn_dash_deprecation(self, opt, section):503        if section in (504            'options.extras_require',505            'options.data_files',506        ):507            return opt508 509        underscore_opt = opt.replace('-', '_')510        commands = list(511            itertools.chain(512                distutils.command.__all__,513                self._setuptools_commands(),514            )515        )516        if (517            not section.startswith('options')518            and section != 'metadata'519            and section not in commands520        ):521            return underscore_opt522 523        if '-' in opt:524            SetuptoolsDeprecationWarning.emit(525                "Invalid dash-separated options",526                f"""527                Usage of dash-separated {opt!r} will not be supported in future528                versions. Please use the underscore name {underscore_opt!r} instead.529                """,530                see_docs="userguide/declarative_config.html",531                due_date=(2024, 9, 26),532                # Warning initially introduced in 3 Mar 2021533            )534        return underscore_opt535 536    def _setuptools_commands(self):537        try:538            return metadata.distribution('setuptools').entry_points.names539        except metadata.PackageNotFoundError:540            # during bootstrapping, distribution doesn't exist541            return []542 543    def make_option_lowercase(self, opt, section):544        if section != 'metadata' or opt.islower():545            return opt546 547        lowercase_opt = opt.lower()548        SetuptoolsDeprecationWarning.emit(549            "Invalid uppercase configuration",550            f"""551            Usage of uppercase key {opt!r} in {section!r} will not be supported in552            future versions. Please use lowercase {lowercase_opt!r} instead.553            """,554            see_docs="userguide/declarative_config.html",555            due_date=(2024, 9, 26),556            # Warning initially introduced in 6 Mar 2021557        )558        return lowercase_opt559 560    # FIXME: 'Distribution._set_command_options' is too complex (14)561    def _set_command_options(self, command_obj, option_dict=None):  # noqa: C901562        """563        Set the options for 'command_obj' from 'option_dict'.  Basically564        this means copying elements of a dictionary ('option_dict') to565        attributes of an instance ('command').566 567        'command_obj' must be a Command instance.  If 'option_dict' is not568        supplied, uses the standard option dictionary for this command569        (from 'self.command_options').570 571        (Adopted from distutils.dist.Distribution._set_command_options)572        """573        command_name = command_obj.get_command_name()574        if option_dict is None:575            option_dict = self.get_option_dict(command_name)576 577        if DEBUG:578            self.announce("  setting options for '%s' command:" % command_name)579        for option, (source, value) in option_dict.items():580            if DEBUG:581                self.announce("    %s = %s (from %s)" % (option, value, source))582            try:583                bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]584            except AttributeError:585                bool_opts = []586            try:587                neg_opt = command_obj.negative_opt588            except AttributeError:589                neg_opt = {}590 591            try:592                is_string = isinstance(value, str)593                if option in neg_opt and is_string:594                    setattr(command_obj, neg_opt[option], not strtobool(value))595                elif option in bool_opts and is_string:596                    setattr(command_obj, option, strtobool(value))597                elif hasattr(command_obj, option):598                    setattr(command_obj, option, value)599                else:600                    raise DistutilsOptionError(601                        "error in %s: command '%s' has no such option '%s'"602                        % (source, command_name, option)603                    )604            except ValueError as e:605                raise DistutilsOptionError(e) from e606 607    def _get_project_config_files(self, filenames):608        """Add default file and split between INI and TOML"""609        tomlfiles = []610        standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")611        if filenames is not None:612            parts = partition(lambda f: Path(f).suffix == ".toml", filenames)613            filenames = list(parts[0])  # 1st element => predicate is False614            tomlfiles = list(parts[1])  # 2nd element => predicate is True615        elif standard_project_metadata.exists():616            tomlfiles = [standard_project_metadata]617        return filenames, tomlfiles618 619    def parse_config_files(self, filenames=None, ignore_option_errors=False):620        """Parses configuration files from various levels621        and loads configuration.622        """623        inifiles, tomlfiles = self._get_project_config_files(filenames)624 625        self._parse_config_files(filenames=inifiles)626 627        setupcfg.parse_configuration(628            self, self.command_options, ignore_option_errors=ignore_option_errors629        )630        for filename in tomlfiles:631            pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)632 633        self._finalize_requires()634        self._finalize_license_files()635 636    def fetch_build_eggs(self, requires):637        """Resolve pre-setup requirements"""638        from .installer import _fetch_build_eggs639 640        return _fetch_build_eggs(self, requires)641 642    def finalize_options(self):643        """644        Allow plugins to apply arbitrary operations to the645        distribution. Each hook may optionally define a 'order'646        to influence the order of execution. Smaller numbers647        go first and the default is 0.648        """649        group = 'setuptools.finalize_distribution_options'650 651        def by_order(hook):652            return getattr(hook, 'order', 0)653 654        defined = metadata.entry_points(group=group)655        filtered = itertools.filterfalse(self._removed, defined)656        loaded = map(lambda e: e.load(), filtered)657        for ep in sorted(loaded, key=by_order):658            ep(self)659 660    @staticmethod661    def _removed(ep):662        """663        When removing an entry point, if metadata is loaded664        from an older version of Setuptools, that removed665        entry point will attempt to be loaded and will fail.666        See #2765 for more details.667        """668        removed = {669            # removed 2021-09-05670            '2to3_doctests',671        }672        return ep.name in removed673 674    def _finalize_setup_keywords(self):675        for ep in metadata.entry_points(group='distutils.setup_keywords'):676            value = getattr(self, ep.name, None)677            if value is not None:678                ep.load()(self, ep.name, value)679 680    def get_egg_cache_dir(self):681        from . import windows_support682 683        egg_cache_dir = os.path.join(os.curdir, '.eggs')684        if not os.path.exists(egg_cache_dir):685            os.mkdir(egg_cache_dir)686            windows_support.hide_file(egg_cache_dir)687            readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')688            with open(readme_txt_filename, 'w') as f:689                f.write(690                    'This directory contains eggs that were downloaded '691                    'by setuptools to build, test, and run plug-ins.\n\n'692                )693                f.write(694                    'This directory caches those eggs to prevent '695                    'repeated downloads.\n\n'696                )697                f.write('However, it is safe to delete this directory.\n\n')698 699        return egg_cache_dir700 701    def fetch_build_egg(self, req):702        """Fetch an egg needed for building"""703        from .installer import fetch_build_egg704 705        return fetch_build_egg(self, req)706 707    def get_command_class(self, command):708        """Pluggable version of get_command_class()"""709        if command in self.cmdclass:710            return self.cmdclass[command]711 712        eps = metadata.entry_points(group='distutils.commands', name=command)713        for ep in eps:714            self.cmdclass[command] = cmdclass = ep.load()715            return cmdclass716        else:717            return _Distribution.get_command_class(self, command)718 719    def print_commands(self):720        for ep in metadata.entry_points(group='distutils.commands'):721            if ep.name not in self.cmdclass:722                cmdclass = ep.load()723                self.cmdclass[ep.name] = cmdclass724        return _Distribution.print_commands(self)725 726    def get_command_list(self):727        for ep in metadata.entry_points(group='distutils.commands'):728            if ep.name not in self.cmdclass:729                cmdclass = ep.load()730                self.cmdclass[ep.name] = cmdclass731        return _Distribution.get_command_list(self)732 733    def include(self, **attrs):734        """Add items to distribution that are named in keyword arguments735 736        For example, 'dist.include(py_modules=["x"])' would add 'x' to737        the distribution's 'py_modules' attribute, if it was not already738        there.739 740        Currently, this method only supports inclusion for attributes that are741        lists or tuples.  If you need to add support for adding to other742        attributes in this or a subclass, you can add an '_include_X' method,743        where 'X' is the name of the attribute.  The method will be called with744        the value passed to 'include()'.  So, 'dist.include(foo={"bar":"baz"})'745        will try to call 'dist._include_foo({"bar":"baz"})', which can then746        handle whatever special inclusion logic is needed.747        """748        for k, v in attrs.items():749            include = getattr(self, '_include_' + k, None)750            if include:751                include(v)752            else:753                self._include_misc(k, v)754 755    def exclude_package(self, package):756        """Remove packages, modules, and extensions in named package"""757 758        pfx = package + '.'759        if self.packages:760            self.packages = [761                p for p in self.packages if p != package and not p.startswith(pfx)762            ]763 764        if self.py_modules:765            self.py_modules = [766                p for p in self.py_modules if p != package and not p.startswith(pfx)767            ]768 769        if self.ext_modules:770            self.ext_modules = [771                p772                for p in self.ext_modules773                if p.name != package and not p.name.startswith(pfx)774            ]775 776    def has_contents_for(self, package):777        """Return true if 'exclude_package(package)' would do something"""778 779        pfx = package + '.'780 781        for p in self.iter_distribution_names():782            if p == package or p.startswith(pfx):783                return True784 785        return False786 787    def _exclude_misc(self, name, value):788        """Handle 'exclude()' for list/tuple attrs without a special handler"""789        if not isinstance(value, sequence):790            raise DistutilsSetupError(791                "%s: setting must be a list or tuple (%r)" % (name, value)792            )793        try:794            old = getattr(self, name)795        except AttributeError as e:796            raise DistutilsSetupError("%s: No such distribution setting" % name) from e797        if old is not None and not isinstance(old, sequence):798            raise DistutilsSetupError(799                name + ": this setting cannot be changed via include/exclude"800            )801        elif old:802            setattr(self, name, [item for item in old if item not in value])803 804    def _include_misc(self, name, value):805        """Handle 'include()' for list/tuple attrs without a special handler"""806 807        if not isinstance(value, sequence):808            raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value))809        try:810            old = getattr(self, name)811        except AttributeError as e:812            raise DistutilsSetupError("%s: No such distribution setting" % name) from e813        if old is None:814            setattr(self, name, value)815        elif not isinstance(old, sequence):816            raise DistutilsSetupError(817                name + ": this setting cannot be changed via include/exclude"818            )819        else:820            new = [item for item in value if item not in old]821            setattr(self, name, old + new)822 823    def exclude(self, **attrs):824        """Remove items from distribution that are named in keyword arguments825 826        For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from827        the distribution's 'py_modules' attribute.  Excluding packages uses828        the 'exclude_package()' method, so all of the package's contained829        packages, modules, and extensions are also excluded.830 831        Currently, this method only supports exclusion from attributes that are832        lists or tuples.  If you need to add support for excluding from other833        attributes in this or a subclass, you can add an '_exclude_X' method,834        where 'X' is the name of the attribute.  The method will be called with835        the value passed to 'exclude()'.  So, 'dist.exclude(foo={"bar":"baz"})'836        will try to call 'dist._exclude_foo({"bar":"baz"})', which can then837        handle whatever special exclusion logic is needed.838        """839        for k, v in attrs.items():840            exclude = getattr(self, '_exclude_' + k, None)841            if exclude:842                exclude(v)843            else:844                self._exclude_misc(k, v)845 846    def _exclude_packages(self, packages):847        if not isinstance(packages, sequence):848            raise DistutilsSetupError(849                "packages: setting must be a list or tuple (%r)" % (packages,)850            )851        list(map(self.exclude_package, packages))852 853    def _parse_command_opts(self, parser, args):854        # Remove --with-X/--without-X options when processing command args855        self.global_options = self.__class__.global_options856        self.negative_opt = self.__class__.negative_opt857 858        # First, expand any aliases859        command = args[0]860        aliases = self.get_option_dict('aliases')861        while command in aliases:862            src, alias = aliases[command]863            del aliases[command]  # ensure each alias can expand only once!864            import shlex865 866            args[:1] = shlex.split(alias, True)867            command = args[0]868 869        nargs = _Distribution._parse_command_opts(self, parser, args)870 871        # Handle commands that want to consume all remaining arguments872        cmd_class = self.get_command_class(command)873        if getattr(cmd_class, 'command_consumes_arguments', None):874            self.get_option_dict(command)['args'] = ("command line", nargs)875            if nargs is not None:876                return []877 878        return nargs879 880    def get_cmdline_options(self):881        """Return a '{cmd: {opt:val}}' map of all command-line options882 883        Option names are all long, but do not include the leading '--', and884        contain dashes rather than underscores.  If the option doesn't take885        an argument (e.g. '--quiet'), the 'val' is 'None'.886 887        Note that options provided by config files are intentionally excluded.888        """889 890        d = {}891 892        for cmd, opts in self.command_options.items():893            for opt, (src, val) in opts.items():894                if src != "command line":895                    continue896 897                opt = opt.replace('_', '-')898 899                if val == 0:900                    cmdobj = self.get_command_obj(cmd)901                    neg_opt = self.negative_opt.copy()902                    neg_opt.update(getattr(cmdobj, 'negative_opt', {}))903                    for neg, pos in neg_opt.items():904                        if pos == opt:905                            opt = neg906                            val = None907                            break908                    else:909                        raise AssertionError("Shouldn't be able to get here")910 911                elif val == 1:912                    val = None913 914                d.setdefault(cmd, {})[opt] = val915 916        return d917 918    def iter_distribution_names(self):919        """Yield all packages, modules, and extension names in distribution"""920 921        yield from self.packages or ()922 923        yield from self.py_modules or ()924 925        for ext in self.ext_modules or ():926            if isinstance(ext, tuple):927                name, buildinfo = ext928            else:929                name = ext.name930            if name.endswith('module'):931                name = name[:-6]932            yield name933 934    def handle_display_options(self, option_order):935        """If there were any non-global "display-only" options936        (--help-commands or the metadata display options) on the command937        line, display the requested info and return true; else return938        false.939        """940        import sys941 942        if self.help_commands:943            return _Distribution.handle_display_options(self, option_order)944 945        # Stdout may be StringIO (e.g. in tests)946        if not isinstance(sys.stdout, io.TextIOWrapper):947            return _Distribution.handle_display_options(self, option_order)948 949        # Don't wrap stdout if utf-8 is already the encoding. Provides950        #  workaround for #334.951        if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):952            return _Distribution.handle_display_options(self, option_order)953 954        # Print metadata in UTF-8 no matter the platform955        encoding = sys.stdout.encoding956        sys.stdout.reconfigure(encoding='utf-8')957        try:958            return _Distribution.handle_display_options(self, option_order)959        finally:960            sys.stdout.reconfigure(encoding=encoding)961 962    def run_command(self, command):963        self.set_defaults()964        # Postpone defaults until all explicit configuration is considered965        # (setup() args, config files, command line and plugins)966 967        super().run_command(command)968 969 970class DistDeprecationWarning(SetuptoolsDeprecationWarning):971    """Class for warning about deprecations in dist in972    setuptools. Not ignored by default, unlike DeprecationWarning."""973 
codekingpro/portable-devtools · Team Ai