Repository: pypa/setuptools
Pull request #1286 (2018-03-06 04:22:34)

Updates for Metadata 2.1 (PEP 566)
This PR makes the necessary updates for Metadata 2.1 ([PEP 566](https://www.python.org/dev/peps/pep-0566/)):

* Handles the new `Description-Content-Type` and `Provides-Extra` fields
  * The former was already added in #1075, I made a slight tweak from #1207 to only set this field if it is present
* Correctly sets the `Version` field to `2.1` if either of the new fields are present


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index e518b1994..7c3a46c67 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,8 @@
+v38.6.0
+-------
+
+* #1286: Add support for Metadata 2.1 (PEP 566).
+
 v38.5.2
 -------
 
diff --git a/setuptools/dist.py b/setuptools/dist.py
index c2bfdbc7c..33ceb4042 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -46,6 +46,8 @@ def write_pkg_file(self, file):
     # Setuptools specific for PEP 345
     if hasattr(self, 'python_requires') or self.project_urls:
         version = '1.2'
+    if self.long_description_content_type or self.provides_extras:
+        version = '2.1'
 
     file.write('Metadata-Version: %s\n' % version)
     file.write('Name: %s\n' % self.get_name())
@@ -60,10 +62,6 @@ def write_pkg_file(self, file):
     for project_url in self.project_urls.items():
         file.write('Project-URL: %s, %s\n' % project_url)
 
-    long_desc_content_type = \
-        self.long_description_content_type or 'UNKNOWN'
-    file.write('Description-Content-Type: %s\n' % long_desc_content_type)
-
     long_desc = rfc822_escape(self.get_long_description())
     file.write('Description: %s\n' % long_desc)
 
@@ -83,6 +81,16 @@ def write_pkg_file(self, file):
     if hasattr(self, 'python_requires'):
         file.write('Requires-Python: %s\n' % self.python_requires)
 
+    # PEP 566
+    if self.long_description_content_type:
+        file.write(
+            'Description-Content-Type: %s\n' %
+            self.long_description_content_type
+        )
+    if self.provides_extras:
+        for extra in self.provides_extras:
+            file.write('Provides-Extra: %s\n' % extra)
+
 
 # from Python 3.4
 def write_pkg_info(self, base_dir):
@@ -339,6 +347,9 @@ class Distribution(Distribution_parse_config_files, _Distribution):
         self.metadata.long_description_content_type = attrs.get(
             'long_description_content_type'
         )
+        self.metadata.provides_extras = getattr(
+            self.metadata, 'provides_extras', set()
+        )
 
         if isinstance(self.metadata.version, numbers.Number):
             # Some people apparently take "version number" too literally :)
@@ -372,6 +383,14 @@ class Distribution(Distribution_parse_config_files, _Distribution):
         """
         if getattr(self, 'python_requires', None):
             self.metadata.python_requires = self.python_requires
+
+        if getattr(self, 'extras_require', None):
+            for extra in self.extras_require.keys():
+                # Since this gets called multiple times at points where the
+                # keys have become 'converted' extras, ensure that we are only
+                # truly adding extras we haven't seen before here.
+                self.metadata.provides_extras.add(extra.split(':')[0])
+
         self._convert_extras_requirements()
         self._move_install_requirements_markers()
 


Test patch:
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 383e0d307..abb953a8f 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -546,6 +546,7 @@ class TestOptions:
                 'pdf': ['ReportLab>=1.2', 'RXP'],
                 'rest': ['docutils>=0.3', 'pack==1.1,==1.3']
             }
+            assert dist.metadata.provides_extras == set(['pdf', 'rest'])
 
     def test_entry_points(self, tmpdir):
         _, config = fake_env(
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 7d12434e1..ff5fa0a30 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -420,6 +420,24 @@ class TestEggInfo(object):
             self._run_install_command(tmpdir_cwd, env)
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
 
+    def test_provides_extra(self, tmpdir_cwd, env):
+        self._setup_script_with_requires(
+            'extras_require={"foobar": ["barbazquux"]},')
+        environ = os.environ.copy().update(
+            HOME=env.paths['home'],
+        )
+        code, data = environment.run_setup_py(
+            cmd=['egg_info'],
+            pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+            data_stream=1,
+            env=environ,
+        )
+        egg_info_dir = os.path.join('.', 'foo.egg-info')
+        with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
+            pkg_info_lines = pkginfo_file.read().split('\n')
+        assert 'Provides-Extra: foobar' in pkg_info_lines
+        assert 'Metadata-Version: 2.1' in pkg_info_lines
+
     def test_long_description_content_type(self, tmpdir_cwd, env):
         # Test that specifying a `long_description_content_type` keyword arg to
         # the `setup` function results in writing a `Description-Content-Type`
@@ -444,6 +462,7 @@ class TestEggInfo(object):
             pkg_info_lines = pkginfo_file.read().split('\n')
         expected_line = 'Description-Content-Type: text/markdown'
         assert expected_line in pkg_info_lines
+        assert 'Metadata-Version: 2.1' in pkg_info_lines
 
     def test_project_urls(self, tmpdir_cwd, env):
         # Test that specifying a `project_urls` dict to the `setup`