diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-03-08 16:27:59 +0000 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2023-05-03 14:09:16 +0100 |
| commit | 933db3395074ded71336ad813160ede2ec8fb437 (patch) | |
| tree | 0a98234596e6dcc72841b605724a5c209fe367e9 | |
| parent | 64f35f051a26631fdce0437099d86e1ca524e19d (diff) | |
| download | python-setuptools-git-933db3395074ded71336ad813160ede2ec8fb437.tar.gz | |
Extract core metadata logic into dedicated module
| -rw-r--r-- | setuptools/_core_metadata.py | 191 | ||||
| -rw-r--r-- | setuptools/dist.py | 186 | ||||
| -rw-r--r-- | setuptools/monkey.py | 3 | ||||
| -rw-r--r-- | setuptools/tests/test_core_metadata.py | 245 | ||||
| -rw-r--r-- | setuptools/tests/test_dist.py | 231 |
5 files changed, 439 insertions, 417 deletions
diff --git a/setuptools/_core_metadata.py b/setuptools/_core_metadata.py new file mode 100644 index 00000000..750b09b3 --- /dev/null +++ b/setuptools/_core_metadata.py @@ -0,0 +1,191 @@ +""" +Handling of Core Metadata for Python packages (including reading and writing). + +See: https://packaging.python.org/en/latest/specifications/core-metadata/ +""" +import textwrap +from email import message_from_file +from email.message import Message +from typing import Optional, List + +from distutils.util import rfc822_escape + +from .extern.packaging.version import Version +from .warnings import SetuptoolsDeprecationWarning + + +def get_metadata_version(self): + mv = getattr(self, 'metadata_version', None) + if mv is None: + mv = Version('2.1') + self.metadata_version = mv + return mv + + +def rfc822_unescape(content: str) -> str: + """Reverse RFC-822 escaping by removing leading whitespaces from content.""" + lines = content.splitlines() + if len(lines) == 1: + return lines[0].lstrip() + return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:])))) + + +def _read_field_from_msg(msg: Message, field: str) -> Optional[str]: + """Read Message header field.""" + value = msg[field] + if value == 'UNKNOWN': + return None + return value + + +def _read_field_unescaped_from_msg(msg: Message, field: str) -> Optional[str]: + """Read Message header field and apply rfc822_unescape.""" + value = _read_field_from_msg(msg, field) + if value is None: + return value + return rfc822_unescape(value) + + +def _read_list_from_msg(msg: Message, field: str) -> Optional[List[str]]: + """Read Message header field and return all results as list.""" + values = msg.get_all(field, None) + if values == []: + return None + return values + + +def _read_payload_from_msg(msg: Message) -> Optional[str]: + value = msg.get_payload().strip() + if value == 'UNKNOWN' or not value: + return None + return value + + +def read_pkg_file(self, file): + """Reads the metadata values from a file object.""" + msg = message_from_file(file) + + self.metadata_version = Version(msg['metadata-version']) + self.name = _read_field_from_msg(msg, 'name') + self.version = _read_field_from_msg(msg, 'version') + self.description = _read_field_from_msg(msg, 'summary') + # we are filling author only. + self.author = _read_field_from_msg(msg, 'author') + self.maintainer = None + self.author_email = _read_field_from_msg(msg, 'author-email') + self.maintainer_email = None + self.url = _read_field_from_msg(msg, 'home-page') + self.download_url = _read_field_from_msg(msg, 'download-url') + self.license = _read_field_unescaped_from_msg(msg, 'license') + + self.long_description = _read_field_unescaped_from_msg(msg, 'description') + if ( + self.long_description is None and + self.metadata_version >= Version('2.1') + ): + self.long_description = _read_payload_from_msg(msg) + self.description = _read_field_from_msg(msg, 'summary') + + if 'keywords' in msg: + self.keywords = _read_field_from_msg(msg, 'keywords').split(',') + + self.platforms = _read_list_from_msg(msg, 'platform') + self.classifiers = _read_list_from_msg(msg, 'classifier') + + # PEP 314 - these fields only exist in 1.1 + if self.metadata_version == Version('1.1'): + self.requires = _read_list_from_msg(msg, 'requires') + self.provides = _read_list_from_msg(msg, 'provides') + self.obsoletes = _read_list_from_msg(msg, 'obsoletes') + else: + self.requires = None + self.provides = None + self.obsoletes = None + + self.license_files = _read_list_from_msg(msg, 'license-file') + + +def single_line(val): + """ + Quick and dirty validation for Summary pypa/setuptools#1390. + """ + if '\n' in val: + # TODO: Replace with `raise ValueError("newlines not allowed")` + # after reviewing #2893. + msg = "newlines are not allowed in `summary` and will break in the future" + SetuptoolsDeprecationWarning.emit("Invalid config.", msg) + # due_date is undefined. Controversial change, there was a lot of push back. + val = val.strip().split('\n')[0] + return val + + +# Based on Python 3.5 version +def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME + """Write the PKG-INFO format data to a file object.""" + version = self.get_metadata_version() + + def write_field(key, value): + file.write("%s: %s\n" % (key, value)) + + write_field('Metadata-Version', str(version)) + write_field('Name', self.get_name()) + write_field('Version', self.get_version()) + + summary = self.get_description() + if summary: + write_field('Summary', single_line(summary)) + + optional_fields = ( + ('Home-page', 'url'), + ('Download-URL', 'download_url'), + ('Author', 'author'), + ('Author-email', 'author_email'), + ('Maintainer', 'maintainer'), + ('Maintainer-email', 'maintainer_email'), + ) + + for field, attr in optional_fields: + attr_val = getattr(self, attr, None) + if attr_val is not None: + write_field(field, attr_val) + + license = self.get_license() + if license: + write_field('License', rfc822_escape(license)) + + for project_url in self.project_urls.items(): + write_field('Project-URL', '%s, %s' % project_url) + + keywords = ','.join(self.get_keywords()) + if keywords: + write_field('Keywords', keywords) + + platforms = self.get_platforms() or [] + for platform in platforms: + write_field('Platform', platform) + + self._write_list(file, 'Classifier', self.get_classifiers()) + + # PEP 314 + self._write_list(file, 'Requires', self.get_requires()) + self._write_list(file, 'Provides', self.get_provides()) + self._write_list(file, 'Obsoletes', self.get_obsoletes()) + + # Setuptools specific for PEP 345 + if hasattr(self, 'python_requires'): + write_field('Requires-Python', self.python_requires) + + # PEP 566 + if self.long_description_content_type: + write_field('Description-Content-Type', self.long_description_content_type) + if self.provides_extras: + for extra in self.provides_extras: + write_field('Provides-Extra', extra) + + self._write_list(file, 'License-File', self.license_files or []) + + long_description = self.get_long_description() + if long_description: + file.write("\n%s" % long_description) + if not long_description.endswith("\n"): + file.write("\n") diff --git a/setuptools/dist.py b/setuptools/dist.py index a24e256e..c5c1bc2c 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -15,15 +15,11 @@ from distutils.debug import DEBUG from distutils.fancy_getopt import translate_longopt from glob import iglob import itertools -import textwrap from contextlib import suppress -from typing import List, Optional, Set, TYPE_CHECKING +from typing import List, Optional, Set from pathlib import Path -from email import message_from_file - from distutils.errors import DistutilsOptionError, DistutilsSetupError -from distutils.util import rfc822_escape from setuptools.extern import packaging from setuptools.extern import ordered_set @@ -36,15 +32,12 @@ from setuptools.monkey import get_unpatched from setuptools.config import setupcfg, pyprojecttoml from setuptools.discovery import ConfigDiscovery -from setuptools.extern.packaging import version from . import _reqs from . import _entry_points from . import _normalization from ._importlib import metadata from .warnings import InformationOnly, SetuptoolsDeprecationWarning -if TYPE_CHECKING: - from email.message import Message __import__('setuptools.extern.packaging.specifiers') __import__('setuptools.extern.packaging.version') @@ -60,183 +53,6 @@ def _get_unpatched(cls): return get_unpatched(cls) -def get_metadata_version(self): - mv = getattr(self, 'metadata_version', None) - if mv is None: - mv = version.Version('2.1') - self.metadata_version = mv - return mv - - -def rfc822_unescape(content: str) -> str: - """Reverse RFC-822 escaping by removing leading whitespaces from content.""" - lines = content.splitlines() - if len(lines) == 1: - return lines[0].lstrip() - return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:])))) - - -def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]: - """Read Message header field.""" - value = msg[field] - if value == 'UNKNOWN': - return None - return value - - -def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]: - """Read Message header field and apply rfc822_unescape.""" - value = _read_field_from_msg(msg, field) - if value is None: - return value - return rfc822_unescape(value) - - -def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]: - """Read Message header field and return all results as list.""" - values = msg.get_all(field, None) - if values == []: - return None - return values - - -def _read_payload_from_msg(msg: "Message") -> Optional[str]: - value = msg.get_payload().strip() - if value == 'UNKNOWN' or not value: - return None - return value - - -def read_pkg_file(self, file): - """Reads the metadata values from a file object.""" - msg = message_from_file(file) - - self.metadata_version = version.Version(msg['metadata-version']) - self.name = _read_field_from_msg(msg, 'name') - self.version = _read_field_from_msg(msg, 'version') - self.description = _read_field_from_msg(msg, 'summary') - # we are filling author only. - self.author = _read_field_from_msg(msg, 'author') - self.maintainer = None - self.author_email = _read_field_from_msg(msg, 'author-email') - self.maintainer_email = None - self.url = _read_field_from_msg(msg, 'home-page') - self.download_url = _read_field_from_msg(msg, 'download-url') - self.license = _read_field_unescaped_from_msg(msg, 'license') - - self.long_description = _read_field_unescaped_from_msg(msg, 'description') - if ( - self.long_description is None and - self.metadata_version >= version.Version('2.1') - ): - self.long_description = _read_payload_from_msg(msg) - self.description = _read_field_from_msg(msg, 'summary') - - if 'keywords' in msg: - self.keywords = _read_field_from_msg(msg, 'keywords').split(',') - - self.platforms = _read_list_from_msg(msg, 'platform') - self.classifiers = _read_list_from_msg(msg, 'classifier') - - # PEP 314 - these fields only exist in 1.1 - if self.metadata_version == version.Version('1.1'): - self.requires = _read_list_from_msg(msg, 'requires') - self.provides = _read_list_from_msg(msg, 'provides') - self.obsoletes = _read_list_from_msg(msg, 'obsoletes') - else: - self.requires = None - self.provides = None - self.obsoletes = None - - self.license_files = _read_list_from_msg(msg, 'license-file') - - -def single_line(val): - """ - Quick and dirty validation for Summary pypa/setuptools#1390. - """ - if '\n' in val: - # TODO: Replace with `raise ValueError("newlines not allowed")` - # after reviewing #2893. - msg = "newlines are not allowed in `summary` and will break in the future" - SetuptoolsDeprecationWarning.emit("Invalid config.", msg) - # due_date is undefined. Controversial change, there was a lot of push back. - val = val.strip().split('\n')[0] - return val - - -# Based on Python 3.5 version -def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME - """Write the PKG-INFO format data to a file object.""" - version = self.get_metadata_version() - - def write_field(key, value): - file.write("%s: %s\n" % (key, value)) - - write_field('Metadata-Version', str(version)) - write_field('Name', self.get_name()) - write_field('Version', self.get_version()) - - summary = self.get_description() - if summary: - write_field('Summary', single_line(summary)) - - optional_fields = ( - ('Home-page', 'url'), - ('Download-URL', 'download_url'), - ('Author', 'author'), - ('Author-email', 'author_email'), - ('Maintainer', 'maintainer'), - ('Maintainer-email', 'maintainer_email'), - ) - - for field, attr in optional_fields: - attr_val = getattr(self, attr, None) - if attr_val is not None: - write_field(field, attr_val) - - license = self.get_license() - if license: - write_field('License', rfc822_escape(license)) - - for project_url in self.project_urls.items(): - write_field('Project-URL', '%s, %s' % project_url) - - keywords = ','.join(self.get_keywords()) - if keywords: - write_field('Keywords', keywords) - - platforms = self.get_platforms() or [] - for platform in platforms: - write_field('Platform', platform) - - self._write_list(file, 'Classifier', self.get_classifiers()) - - # PEP 314 - self._write_list(file, 'Requires', self.get_requires()) - self._write_list(file, 'Provides', self.get_provides()) - self._write_list(file, 'Obsoletes', self.get_obsoletes()) - - # Setuptools specific for PEP 345 - if hasattr(self, 'python_requires'): - write_field('Requires-Python', self.python_requires) - - # PEP 566 - if self.long_description_content_type: - write_field('Description-Content-Type', self.long_description_content_type) - if self.provides_extras: - for extra in self.provides_extras: - write_field('Provides-Extra', extra) - - self._write_list(file, 'License-File', self.license_files or []) - - long_description = self.get_long_description() - if long_description: - file.write("\n%s" % long_description) - if not long_description.endswith("\n"): - file.write("\n") - - sequence = tuple, list diff --git a/setuptools/monkey.py b/setuptools/monkey.py index 50653fc7..23a5832d 100644 --- a/setuptools/monkey.py +++ b/setuptools/monkey.py @@ -11,6 +11,7 @@ from importlib import import_module import inspect import setuptools +import setuptools._core_metadata __all__ = [] """ @@ -100,7 +101,7 @@ def patch_all(): def _patch_distribution_metadata(): """Patch write_pkg_file and read_pkg_file for higher metadata standards""" for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'): - new_val = getattr(setuptools.dist, attr) + new_val = getattr(setuptools._core_metadata, attr) setattr(distutils.dist.DistributionMetadata, attr, new_val) diff --git a/setuptools/tests/test_core_metadata.py b/setuptools/tests/test_core_metadata.py new file mode 100644 index 00000000..19d1af1e --- /dev/null +++ b/setuptools/tests/test_core_metadata.py @@ -0,0 +1,245 @@ +import functools +import io + +import pytest + +from setuptools import sic +from setuptools.dist import Distribution +from setuptools._core_metadata import rfc822_escape, rfc822_unescape + + +EXAMPLE_BASE_INFO = dict( + name="package", + version="0.0.1", + author="Foo Bar", + author_email="foo@bar.net", + long_description="Long\ndescription", + description="Short description", + keywords=["one", "two"], +) + + +@pytest.mark.parametrize( + 'content, result', + ( + pytest.param( + "Just a single line", + None, + id="single_line", + ), + pytest.param( + "Multiline\nText\nwithout\nextra indents\n", + None, + id="multiline", + ), + pytest.param( + "Multiline\n With\n\nadditional\n indentation", + None, + id="multiline_with_indentation", + ), + pytest.param( + " Leading whitespace", + "Leading whitespace", + id="remove_leading_whitespace", + ), + pytest.param( + " Leading whitespace\nIn\n Multiline comment", + "Leading whitespace\nIn\n Multiline comment", + id="remove_leading_whitespace_multiline", + ), + ) +) +def test_rfc822_unescape(content, result): + assert (result or content) == rfc822_unescape(rfc822_escape(content)) + + +def __read_test_cases(): + base = EXAMPLE_BASE_INFO + + params = functools.partial(dict, base) + + test_cases = [ + ('Metadata version 1.0', params()), + ('Metadata Version 1.0: Short long description', params( + long_description='Short long description', + )), + ('Metadata version 1.1: Classifiers', params( + classifiers=[ + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'License :: OSI Approved :: MIT License', + ], + )), + ('Metadata version 1.1: Download URL', params( + download_url='https://example.com', + )), + ('Metadata Version 1.2: Requires-Python', params( + python_requires='>=3.7', + )), + pytest.param( + 'Metadata Version 1.2: Project-Url', + params(project_urls=dict(Foo='https://example.bar')), + marks=pytest.mark.xfail( + reason="Issue #1578: project_urls not read", + ), + ), + ('Metadata Version 2.1: Long Description Content Type', params( + long_description_content_type='text/x-rst; charset=UTF-8', + )), + ('License', params(license='MIT', )), + ('License multiline', params( + license='This is a long license \nover multiple lines', + )), + pytest.param( + 'Metadata Version 2.1: Provides Extra', + params(provides_extras=['foo', 'bar']), + marks=pytest.mark.xfail(reason="provides_extras not read"), + ), + ('Missing author', dict( + name='foo', + version='1.0.0', + author_email='snorri@sturluson.name', + )), + ('Missing author e-mail', dict( + name='foo', + version='1.0.0', + author='Snorri Sturluson', + )), + ('Missing author and e-mail', dict( + name='foo', + version='1.0.0', + )), + ('Bypass normalized version', dict( + name='foo', + version=sic('1.0.0a'), + )), + ] + + return test_cases + + +@pytest.mark.parametrize('name,attrs', __read_test_cases()) +def test_read_metadata(name, attrs): + dist = Distribution(attrs) + metadata_out = dist.metadata + dist_class = metadata_out.__class__ + + # Write to PKG_INFO and then load into a new metadata object + PKG_INFO = io.StringIO() + + metadata_out.write_pkg_file(PKG_INFO) + + PKG_INFO.seek(0) + metadata_in = dist_class() + metadata_in.read_pkg_file(PKG_INFO) + + tested_attrs = [ + ('name', dist_class.get_name), + ('version', dist_class.get_version), + ('author', dist_class.get_contact), + ('author_email', dist_class.get_contact_email), + ('metadata_version', dist_class.get_metadata_version), + ('provides', dist_class.get_provides), + ('description', dist_class.get_description), + ('long_description', dist_class.get_long_description), + ('download_url', dist_class.get_download_url), + ('keywords', dist_class.get_keywords), + ('platforms', dist_class.get_platforms), + ('obsoletes', dist_class.get_obsoletes), + ('requires', dist_class.get_requires), + ('classifiers', dist_class.get_classifiers), + ('project_urls', lambda s: getattr(s, 'project_urls', {})), + ('provides_extras', lambda s: getattr(s, 'provides_extras', set())), + ] + + for attr, getter in tested_attrs: + assert getter(metadata_in) == getter(metadata_out) + + +def __maintainer_test_cases(): + attrs = {"name": "package", + "version": "1.0", + "description": "xxx"} + + def merge_dicts(d1, d2): + d1 = d1.copy() + d1.update(d2) + + return d1 + + test_cases = [ + ('No author, no maintainer', attrs.copy()), + ('Author (no e-mail), no maintainer', merge_dicts( + attrs, + {'author': 'Author Name'})), + ('Author (e-mail), no maintainer', merge_dicts( + attrs, + {'author': 'Author Name', + 'author_email': 'author@name.com'})), + ('No author, maintainer (no e-mail)', merge_dicts( + attrs, + {'maintainer': 'Maintainer Name'})), + ('No author, maintainer (e-mail)', merge_dicts( + attrs, + {'maintainer': 'Maintainer Name', + 'maintainer_email': 'maintainer@name.com'})), + ('Author (no e-mail), Maintainer (no-email)', merge_dicts( + attrs, + {'author': 'Author Name', + 'maintainer': 'Maintainer Name'})), + ('Author (e-mail), Maintainer (e-mail)', merge_dicts( + attrs, + {'author': 'Author Name', + 'author_email': 'author@name.com', + 'maintainer': 'Maintainer Name', + 'maintainer_email': 'maintainer@name.com'})), + ('No author (e-mail), no maintainer (e-mail)', merge_dicts( + attrs, + {'author_email': 'author@name.com', + 'maintainer_email': 'maintainer@name.com'})), + ('Author unicode', merge_dicts( + attrs, + {'author': '鉄沢寛'})), + ('Maintainer unicode', merge_dicts( + attrs, + {'maintainer': 'Jan Łukasiewicz'})), + ] + + return test_cases + + +@pytest.mark.parametrize('name,attrs', __maintainer_test_cases()) +def test_maintainer_author(name, attrs, tmpdir): + tested_keys = { + 'author': 'Author', + 'author_email': 'Author-email', + 'maintainer': 'Maintainer', + 'maintainer_email': 'Maintainer-email', + } + + # Generate a PKG-INFO file + dist = Distribution(attrs) + fn = tmpdir.mkdir('pkg_info') + fn_s = str(fn) + + dist.metadata.write_pkg_info(fn_s) + + with io.open(str(fn.join('PKG-INFO')), 'r', encoding='utf-8') as f: + raw_pkg_lines = f.readlines() + + # Drop blank lines and strip lines from default description + pkg_lines = list(filter(None, raw_pkg_lines[:-2])) + + pkg_lines_set = set(pkg_lines) + + # Duplicate lines should not be generated + assert len(pkg_lines) == len(pkg_lines_set) + + for fkey, dkey in tested_keys.items(): + val = attrs.get(dkey, None) + if val is None: + for line in pkg_lines: + assert not line.startswith(fkey + ':') + else: + line = '%s: %s' % (fkey, val) + assert line in pkg_lines_set diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index e7d2f5ca..b87c3704 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -1,7 +1,5 @@ -import io import collections import re -import functools import os import urllib.request import urllib.parse @@ -11,10 +9,7 @@ from setuptools.dist import ( check_package_data, DistDeprecationWarning, check_specifier, - rfc822_escape, - rfc822_unescape, ) -from setuptools import sic from setuptools import Distribution from .textwrap import DALS @@ -82,198 +77,6 @@ EXAMPLE_BASE_INFO = dict( ) -def __read_test_cases(): - base = EXAMPLE_BASE_INFO - - params = functools.partial(dict, base) - - test_cases = [ - ('Metadata version 1.0', params()), - ('Metadata Version 1.0: Short long description', params( - long_description='Short long description', - )), - ('Metadata version 1.1: Classifiers', params( - classifiers=[ - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'License :: OSI Approved :: MIT License', - ], - )), - ('Metadata version 1.1: Download URL', params( - download_url='https://example.com', - )), - ('Metadata Version 1.2: Requires-Python', params( - python_requires='>=3.7', - )), - pytest.param( - 'Metadata Version 1.2: Project-Url', - params(project_urls=dict(Foo='https://example.bar')), - marks=pytest.mark.xfail( - reason="Issue #1578: project_urls not read", - ), - ), - ('Metadata Version 2.1: Long Description Content Type', params( - long_description_content_type='text/x-rst; charset=UTF-8', - )), - ('License', params(license='MIT', )), - ('License multiline', params( - license='This is a long license \nover multiple lines', - )), - pytest.param( - 'Metadata Version 2.1: Provides Extra', - params(provides_extras=['foo', 'bar']), - marks=pytest.mark.xfail(reason="provides_extras not read"), - ), - ('Missing author', dict( - name='foo', - version='1.0.0', - author_email='snorri@sturluson.name', - )), - ('Missing author e-mail', dict( - name='foo', - version='1.0.0', - author='Snorri Sturluson', - )), - ('Missing author and e-mail', dict( - name='foo', - version='1.0.0', - )), - ('Bypass normalized version', dict( - name='foo', - version=sic('1.0.0a'), - )), - ] - - return test_cases - - -@pytest.mark.parametrize('name,attrs', __read_test_cases()) -def test_read_metadata(name, attrs): - dist = Distribution(attrs) - metadata_out = dist.metadata - dist_class = metadata_out.__class__ - - # Write to PKG_INFO and then load into a new metadata object - PKG_INFO = io.StringIO() - - metadata_out.write_pkg_file(PKG_INFO) - - PKG_INFO.seek(0) - metadata_in = dist_class() - metadata_in.read_pkg_file(PKG_INFO) - - tested_attrs = [ - ('name', dist_class.get_name), - ('version', dist_class.get_version), - ('author', dist_class.get_contact), - ('author_email', dist_class.get_contact_email), - ('metadata_version', dist_class.get_metadata_version), - ('provides', dist_class.get_provides), - ('description', dist_class.get_description), - ('long_description', dist_class.get_long_description), - ('download_url', dist_class.get_download_url), - ('keywords', dist_class.get_keywords), - ('platforms', dist_class.get_platforms), - ('obsoletes', dist_class.get_obsoletes), - ('requires', dist_class.get_requires), - ('classifiers', dist_class.get_classifiers), - ('project_urls', lambda s: getattr(s, 'project_urls', {})), - ('provides_extras', lambda s: getattr(s, 'provides_extras', set())), - ] - - for attr, getter in tested_attrs: - assert getter(metadata_in) == getter(metadata_out) - - -def __maintainer_test_cases(): - attrs = {"name": "package", - "version": "1.0", - "description": "xxx"} - - def merge_dicts(d1, d2): - d1 = d1.copy() - d1.update(d2) - - return d1 - - test_cases = [ - ('No author, no maintainer', attrs.copy()), - ('Author (no e-mail), no maintainer', merge_dicts( - attrs, - {'author': 'Author Name'})), - ('Author (e-mail), no maintainer', merge_dicts( - attrs, - {'author': 'Author Name', - 'author_email': 'author@name.com'})), - ('No author, maintainer (no e-mail)', merge_dicts( - attrs, - {'maintainer': 'Maintainer Name'})), - ('No author, maintainer (e-mail)', merge_dicts( - attrs, - {'maintainer': 'Maintainer Name', - 'maintainer_email': 'maintainer@name.com'})), - ('Author (no e-mail), Maintainer (no-email)', merge_dicts( - attrs, - {'author': 'Author Name', - 'maintainer': 'Maintainer Name'})), - ('Author (e-mail), Maintainer (e-mail)', merge_dicts( - attrs, - {'author': 'Author Name', - 'author_email': 'author@name.com', - 'maintainer': 'Maintainer Name', - 'maintainer_email': 'maintainer@name.com'})), - ('No author (e-mail), no maintainer (e-mail)', merge_dicts( - attrs, - {'author_email': 'author@name.com', - 'maintainer_email': 'maintainer@name.com'})), - ('Author unicode', merge_dicts( - attrs, - {'author': '鉄沢寛'})), - ('Maintainer unicode', merge_dicts( - attrs, - {'maintainer': 'Jan Łukasiewicz'})), - ] - - return test_cases - - -@pytest.mark.parametrize('name,attrs', __maintainer_test_cases()) -def test_maintainer_author(name, attrs, tmpdir): - tested_keys = { - 'author': 'Author', - 'author_email': 'Author-email', - 'maintainer': 'Maintainer', - 'maintainer_email': 'Maintainer-email', - } - - # Generate a PKG-INFO file - dist = Distribution(attrs) - fn = tmpdir.mkdir('pkg_info') - fn_s = str(fn) - - dist.metadata.write_pkg_info(fn_s) - - with io.open(str(fn.join('PKG-INFO')), 'r', encoding='utf-8') as f: - raw_pkg_lines = f.readlines() - - # Drop blank lines and strip lines from default description - pkg_lines = list(filter(None, raw_pkg_lines[:-2])) - - pkg_lines_set = set(pkg_lines) - - # Duplicate lines should not be generated - assert len(pkg_lines) == len(pkg_lines_set) - - for fkey, dkey in tested_keys.items(): - val = attrs.get(dkey, None) - if val is None: - for line in pkg_lines: - assert not line.startswith(fkey + ':') - else: - line = '%s: %s' % (fkey, val) - assert line in pkg_lines_set - - def test_provides_extras_deterministic_order(): extras = collections.OrderedDict() extras['a'] = ['foo'] @@ -347,40 +150,6 @@ def test_check_specifier(): dist = Distribution(attrs) -@pytest.mark.parametrize( - 'content, result', - ( - pytest.param( - "Just a single line", - None, - id="single_line", - ), - pytest.param( - "Multiline\nText\nwithout\nextra indents\n", - None, - id="multiline", - ), - pytest.param( - "Multiline\n With\n\nadditional\n indentation", - None, - id="multiline_with_indentation", - ), - pytest.param( - " Leading whitespace", - "Leading whitespace", - id="remove_leading_whitespace", - ), - pytest.param( - " Leading whitespace\nIn\n Multiline comment", - "Leading whitespace\nIn\n Multiline comment", - id="remove_leading_whitespace_multiline", - ), - ) -) -def test_rfc822_unescape(content, result): - assert (result or content) == rfc822_unescape(rfc822_escape(content)) - - def test_metadata_name(): with pytest.raises(DistutilsSetupError, match='missing.*name'): Distribution()._validate_metadata() |
