Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_core_metadata.py269 linesDownload Raw Back to setuptools
1"""2Handling of Core Metadata for Python packages (including reading and writing).3 4See: https://packaging.python.org/en/latest/specifications/core-metadata/5"""6 7import os8import stat9import textwrap10from email import message_from_file11from email.message import Message12from tempfile import NamedTemporaryFile13from typing import Optional, List14 15from distutils.util import rfc822_escape16 17from . import _normalization, _reqs18from .extern.packaging.markers import Marker19from .extern.packaging.requirements import Requirement20from .extern.packaging.utils import canonicalize_name21from .extern.packaging.version import Version22from .warnings import SetuptoolsDeprecationWarning23 24 25def get_metadata_version(self):26    mv = getattr(self, 'metadata_version', None)27    if mv is None:28        mv = Version('2.1')29        self.metadata_version = mv30    return mv31 32 33def rfc822_unescape(content: str) -> str:34    """Reverse RFC-822 escaping by removing leading whitespaces from content."""35    lines = content.splitlines()36    if len(lines) == 1:37        return lines[0].lstrip()38    return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:]))))39 40 41def _read_field_from_msg(msg: Message, field: str) -> Optional[str]:42    """Read Message header field."""43    value = msg[field]44    if value == 'UNKNOWN':45        return None46    return value47 48 49def _read_field_unescaped_from_msg(msg: Message, field: str) -> Optional[str]:50    """Read Message header field and apply rfc822_unescape."""51    value = _read_field_from_msg(msg, field)52    if value is None:53        return value54    return rfc822_unescape(value)55 56 57def _read_list_from_msg(msg: Message, field: str) -> Optional[List[str]]:58    """Read Message header field and return all results as list."""59    values = msg.get_all(field, None)60    if values == []:61        return None62    return values63 64 65def _read_payload_from_msg(msg: Message) -> Optional[str]:66    value = str(msg.get_payload()).strip()67    if value == 'UNKNOWN' or not value:68        return None69    return value70 71 72def read_pkg_file(self, file):73    """Reads the metadata values from a file object."""74    msg = message_from_file(file)75 76    self.metadata_version = Version(msg['metadata-version'])77    self.name = _read_field_from_msg(msg, 'name')78    self.version = _read_field_from_msg(msg, 'version')79    self.description = _read_field_from_msg(msg, 'summary')80    # we are filling author only.81    self.author = _read_field_from_msg(msg, 'author')82    self.maintainer = None83    self.author_email = _read_field_from_msg(msg, 'author-email')84    self.maintainer_email = None85    self.url = _read_field_from_msg(msg, 'home-page')86    self.download_url = _read_field_from_msg(msg, 'download-url')87    self.license = _read_field_unescaped_from_msg(msg, 'license')88 89    self.long_description = _read_field_unescaped_from_msg(msg, 'description')90    if self.long_description is None and self.metadata_version >= Version('2.1'):91        self.long_description = _read_payload_from_msg(msg)92    self.description = _read_field_from_msg(msg, 'summary')93 94    if 'keywords' in msg:95        self.keywords = _read_field_from_msg(msg, 'keywords').split(',')96 97    self.platforms = _read_list_from_msg(msg, 'platform')98    self.classifiers = _read_list_from_msg(msg, 'classifier')99 100    # PEP 314 - these fields only exist in 1.1101    if self.metadata_version == Version('1.1'):102        self.requires = _read_list_from_msg(msg, 'requires')103        self.provides = _read_list_from_msg(msg, 'provides')104        self.obsoletes = _read_list_from_msg(msg, 'obsoletes')105    else:106        self.requires = None107        self.provides = None108        self.obsoletes = None109 110    self.license_files = _read_list_from_msg(msg, 'license-file')111 112 113def single_line(val):114    """115    Quick and dirty validation for Summary pypa/setuptools#1390.116    """117    if '\n' in val:118        # TODO: Replace with `raise ValueError("newlines not allowed")`119        # after reviewing #2893.120        msg = "newlines are not allowed in `summary` and will break in the future"121        SetuptoolsDeprecationWarning.emit("Invalid config.", msg)122        # due_date is undefined. Controversial change, there was a lot of push back.123        val = val.strip().split('\n')[0]124    return val125 126 127def write_pkg_info(self, base_dir):128    """Write the PKG-INFO file into the release tree."""129    temp = ""130    final = os.path.join(base_dir, 'PKG-INFO')131    try:132        # Use a temporary file while writing to avoid race conditions133        # (e.g. `importlib.metadata` reading `.egg-info/PKG-INFO`):134        with NamedTemporaryFile("w", encoding="utf-8", dir=base_dir, delete=False) as f:135            temp = f.name136            self.write_pkg_file(f)137        permissions = stat.S_IMODE(os.lstat(temp).st_mode)138        os.chmod(temp, permissions | stat.S_IRGRP | stat.S_IROTH)139        os.replace(temp, final)  # atomic operation.140    finally:141        if temp and os.path.exists(temp):142            os.remove(temp)143 144 145# Based on Python 3.5 version146def write_pkg_file(self, file):  # noqa: C901  # is too complex (14)  # FIXME147    """Write the PKG-INFO format data to a file object."""148    version = self.get_metadata_version()149 150    def write_field(key, value):151        file.write("%s: %s\n" % (key, value))152 153    write_field('Metadata-Version', str(version))154    write_field('Name', self.get_name())155    write_field('Version', self.get_version())156 157    summary = self.get_description()158    if summary:159        write_field('Summary', single_line(summary))160 161    optional_fields = (162        ('Home-page', 'url'),163        ('Download-URL', 'download_url'),164        ('Author', 'author'),165        ('Author-email', 'author_email'),166        ('Maintainer', 'maintainer'),167        ('Maintainer-email', 'maintainer_email'),168    )169 170    for field, attr in optional_fields:171        attr_val = getattr(self, attr, None)172        if attr_val is not None:173            write_field(field, attr_val)174 175    license = self.get_license()176    if license:177        write_field('License', rfc822_escape(license))178 179    for project_url in self.project_urls.items():180        write_field('Project-URL', '%s, %s' % project_url)181 182    keywords = ','.join(self.get_keywords())183    if keywords:184        write_field('Keywords', keywords)185 186    platforms = self.get_platforms() or []187    for platform in platforms:188        write_field('Platform', platform)189 190    self._write_list(file, 'Classifier', self.get_classifiers())191 192    # PEP 314193    self._write_list(file, 'Requires', self.get_requires())194    self._write_list(file, 'Provides', self.get_provides())195    self._write_list(file, 'Obsoletes', self.get_obsoletes())196 197    # Setuptools specific for PEP 345198    if hasattr(self, 'python_requires'):199        write_field('Requires-Python', self.python_requires)200 201    # PEP 566202    if self.long_description_content_type:203        write_field('Description-Content-Type', self.long_description_content_type)204 205    self._write_list(file, 'License-File', self.license_files or [])206    _write_requirements(self, file)207 208    long_description = self.get_long_description()209    if long_description:210        file.write("\n%s" % long_description)211        if not long_description.endswith("\n"):212            file.write("\n")213 214 215def _write_requirements(self, file):216    for req in _reqs.parse(self.install_requires):217        file.write(f"Requires-Dist: {req}\n")218 219    processed_extras = {}220    for augmented_extra, reqs in self.extras_require.items():221        # Historically, setuptools allows "augmented extras": `<extra>:<condition>`222        unsafe_extra, _, condition = augmented_extra.partition(":")223        unsafe_extra = unsafe_extra.strip()224        extra = _normalization.safe_extra(unsafe_extra)225 226        if extra:227            _write_provides_extra(file, processed_extras, extra, unsafe_extra)228        for req in _reqs.parse_strings(reqs):229            r = _include_extra(req, extra, condition.strip())230            file.write(f"Requires-Dist: {r}\n")231 232    return processed_extras233 234 235def _include_extra(req: str, extra: str, condition: str) -> Requirement:236    r = Requirement(req)  # create a fresh object that can be modified237    parts = (238        f"({r.marker})" if r.marker else None,239        f"({condition})" if condition else None,240        f"extra == {extra!r}" if extra else None,241    )242    r.marker = Marker(" and ".join(x for x in parts if x))243    return r244 245 246def _write_provides_extra(file, processed_extras, safe, unsafe):247    previous = processed_extras.get(safe)248    if previous == unsafe:249        SetuptoolsDeprecationWarning.emit(250            'Ambiguity during "extra" normalization for dependencies.',251            f"""252            {previous!r} and {unsafe!r} normalize to the same value:\n253                {safe!r}\n254            In future versions, setuptools might halt the build process.255            """,256            see_url="https://peps.python.org/pep-0685/",257        )258    else:259        processed_extras[safe] = unsafe260        file.write(f"Provides-Extra: {safe}\n")261 262 263# from pypa/distutils#244; needed only until that logic is always available264def get_fullname(self):265    return "{}-{}".format(266        canonicalize_name(self.get_name()).replace('-', '_'),267        self.get_version(),268    )269 
codekingpro/portable-devtools · Team Ai