[Python-checkins] bpo-19610: setup() now raises TypeError for invalid types (GH-4519)

Berker Peksag webhook-mailer at python.org
Thu Nov 23 13:34:23 EST 2017


https://github.com/python/cpython/commit/dcaed6b2d954786eb5369ec2e8dfdeefe3cdc6ae
commit: dcaed6b2d954786eb5369ec2e8dfdeefe3cdc6ae
branch: master
author: Berker Peksag <berker.peksag at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-11-23T21:34:20+03:00
summary:

bpo-19610: setup() now raises TypeError for invalid types (GH-4519)

The Distribution class now explicitly raises an
exception when 'classifiers', 'keywords' and
'platforms' fields are not specified as a list.

files:
A Misc/NEWS.d/next/Library/2017-11-23-16-15-55.bpo-19610.Dlca2P.rst
M Doc/distutils/apiref.rst
M Doc/distutils/setupscript.rst
M Doc/whatsnew/3.7.rst
M Lib/distutils/dist.py
M Lib/distutils/tests/test_dist.py

diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst
index 7cde1a0701e..ced8837d373 100644
--- a/Doc/distutils/apiref.rst
+++ b/Doc/distutils/apiref.rst
@@ -285,6 +285,10 @@ the full reference.
    See the :func:`setup` function for a list of keyword arguments accepted  by the
    Distribution constructor. :func:`setup` creates a Distribution instance.
 
+   .. versionchanged:: 3.7
+      :class:`~distutils.core.Distribution` now raises a :exc:`TypeError` if
+      ``classifiers``, ``keywords`` and ``platforms`` fields are not specified
+      as a list.
 
 .. class:: Command
 
diff --git a/Doc/distutils/setupscript.rst b/Doc/distutils/setupscript.rst
index 38e0202e4ac..542ad544843 100644
--- a/Doc/distutils/setupscript.rst
+++ b/Doc/distutils/setupscript.rst
@@ -581,17 +581,19 @@ This information includes:
 |                      | description of the        |                 |        |
 |                      | package                   |                 |        |
 +----------------------+---------------------------+-----------------+--------+
-| ``long_description`` | longer description of the | long string     | \(5)   |
+| ``long_description`` | longer description of the | long string     | \(4)   |
 |                      | package                   |                 |        |
 +----------------------+---------------------------+-----------------+--------+
-| ``download_url``     | location where the        | URL             | \(4)   |
+| ``download_url``     | location where the        | URL             |        |
 |                      | package may be downloaded |                 |        |
 +----------------------+---------------------------+-----------------+--------+
-| ``classifiers``      | a list of classifiers     | list of strings | \(4)   |
+| ``classifiers``      | a list of classifiers     | list of strings | (6)(7) |
 +----------------------+---------------------------+-----------------+--------+
-| ``platforms``        | a list of platforms       | list of strings |        |
+| ``platforms``        | a list of platforms       | list of strings | (6)(8) |
 +----------------------+---------------------------+-----------------+--------+
-| ``license``          | license for the package   | short string    | \(6)   |
+| ``keywords``         | a list of keywords        | list of strings | (6)(8) |
++----------------------+---------------------------+-----------------+--------+
+| ``license``          | license for the package   | short string    | \(5)   |
 +----------------------+---------------------------+-----------------+--------+
 
 Notes:
@@ -607,22 +609,30 @@ Notes:
     provided, distutils lists it as the author in :file:`PKG-INFO`.
 
 (4)
-    These fields should not be used if your package is to be compatible with Python
-    versions prior to 2.2.3 or 2.3.  The list is available from the `PyPI website
-    <https://pypi.python.org/pypi>`_.
-
-(5)
     The ``long_description`` field is used by PyPI when you are
     :ref:`registering <package-register>` a package, to
     :ref:`build its home page <package-display>`.
 
-(6)
+(5)
     The ``license`` field is a text indicating the license covering the
     package where the license is not a selection from the "License" Trove
     classifiers. See the ``Classifier`` field. Notice that
     there's a ``licence`` distribution option which is deprecated but still
     acts as an alias for ``license``.
 
+(6)
+    This field must be a list.
+
+(7)
+    The valid classifiers are listed on
+    `PyPI <http://pypi.python.org/pypi?:action=list_classifiers>`_.
+
+(8)
+    To preserve backward compatibility, this field also accepts a string. If
+    you pass a comma-separated string ``'foo, bar'``, it will be converted to
+    ``['foo', 'bar']``, Otherwise, it will be converted to a list of one
+    string.
+
 'short string'
     A single line of text, not more than 200 characters.
 
@@ -650,7 +660,7 @@ information is sometimes used to indicate sub-releases.  These are
 1.0.1a2
     the second alpha release of the first patch version of 1.0
 
-``classifiers`` are specified in a Python list::
+``classifiers`` must be specified in a list::
 
     setup(...,
           classifiers=[
@@ -671,6 +681,11 @@ information is sometimes used to indicate sub-releases.  These are
               ],
           )
 
+.. versionchanged:: 3.7
+   :class:`~distutils.core.setup` now raises a :exc:`TypeError` if
+   ``classifiers``, ``keywords`` and ``platforms`` fields are not specified
+   as a list.
+
 .. _debug-setup-script:
 
 Debugging the setup script
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 71e8358a420..514c3c293c0 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -298,6 +298,12 @@ README.rst is now included in the list of distutils standard READMEs and
 therefore included in source distributions.
 (Contributed by Ryan Gonzalez in :issue:`11913`.)
 
+:class:`distutils.core.setup` now raises a :exc:`TypeError` if
+``classifiers``, ``keywords`` and ``platforms`` fields are not specified
+as a list.  However, to minimize backwards incompatibility concerns,
+``keywords`` and ``platforms`` fields still accept a comma separated string.
+(Contributed by Berker Peksag in :issue:`19610`.)
+
 http.client
 -----------
 
diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py
index 62a24516cfa..78c29ede6c2 100644
--- a/Lib/distutils/dist.py
+++ b/Lib/distutils/dist.py
@@ -1188,12 +1188,38 @@ def get_long_description(self):
     def get_keywords(self):
         return self.keywords or []
 
+    def set_keywords(self, value):
+        # If 'keywords' is a string, it will be converted to a list
+        # by Distribution.finalize_options(). To maintain backwards
+        # compatibility, do not raise an exception if 'keywords' is
+        # a string.
+        if not isinstance(value, (list, str)):
+            msg = "'keywords' should be a 'list', not %r"
+            raise TypeError(msg % type(value).__name__)
+        self.keywords = value
+
     def get_platforms(self):
         return self.platforms or ["UNKNOWN"]
 
+    def set_platforms(self, value):
+        # If 'platforms' is a string, it will be converted to a list
+        # by Distribution.finalize_options(). To maintain backwards
+        # compatibility, do not raise an exception if 'platforms' is
+        # a string.
+        if not isinstance(value, (list, str)):
+            msg = "'platforms' should be a 'list', not %r"
+            raise TypeError(msg % type(value).__name__)
+        self.platforms = value
+
     def get_classifiers(self):
         return self.classifiers or []
 
+    def set_classifiers(self, value):
+        if not isinstance(value, list):
+            msg = "'classifiers' should be a 'list', not %r"
+            raise TypeError(msg % type(value).__name__)
+        self.classifiers = value
+
     def get_download_url(self):
         return self.download_url or "UNKNOWN"
 
diff --git a/Lib/distutils/tests/test_dist.py b/Lib/distutils/tests/test_dist.py
index 1f104cef675..50b456ec947 100644
--- a/Lib/distutils/tests/test_dist.py
+++ b/Lib/distutils/tests/test_dist.py
@@ -195,6 +195,13 @@ def test_finalize_options(self):
         self.assertEqual(dist.metadata.platforms, ['one', 'two'])
         self.assertEqual(dist.metadata.keywords, ['one', 'two'])
 
+        attrs = {'keywords': 'foo bar',
+                 'platforms': 'foo bar'}
+        dist = Distribution(attrs=attrs)
+        dist.finalize_options()
+        self.assertEqual(dist.metadata.platforms, ['foo bar'])
+        self.assertEqual(dist.metadata.keywords, ['foo bar'])
+
     def test_get_command_packages(self):
         dist = Distribution()
         self.assertEqual(dist.command_packages, None)
@@ -338,9 +345,46 @@ def test_classifier(self):
         attrs = {'name': 'Boa', 'version': '3.0',
                  'classifiers': ['Programming Language :: Python :: 3']}
         dist = Distribution(attrs)
+        self.assertEqual(dist.get_classifiers(),
+                         ['Programming Language :: Python :: 3'])
         meta = self.format_metadata(dist)
         self.assertIn('Metadata-Version: 1.1', meta)
 
+    def test_classifier_invalid_type(self):
+        attrs = {'name': 'Boa', 'version': '3.0',
+                 'classifiers': ('Programming Language :: Python :: 3',)}
+        msg = "'classifiers' should be a 'list', not 'tuple'"
+        with self.assertRaises(TypeError, msg=msg):
+            Distribution(attrs)
+
+    def test_keywords(self):
+        attrs = {'name': 'Monty', 'version': '1.0',
+                 'keywords': ['spam', 'eggs', 'life of brian']}
+        dist = Distribution(attrs)
+        self.assertEqual(dist.get_keywords(),
+                         ['spam', 'eggs', 'life of brian'])
+
+    def test_keywords_invalid_type(self):
+        attrs = {'name': 'Monty', 'version': '1.0',
+                 'keywords': ('spam', 'eggs', 'life of brian')}
+        msg = "'keywords' should be a 'list', not 'tuple'"
+        with self.assertRaises(TypeError, msg=msg):
+            Distribution(attrs)
+
+    def test_platforms(self):
+        attrs = {'name': 'Monty', 'version': '1.0',
+                 'platforms': ['GNU/Linux', 'Some Evil Platform']}
+        dist = Distribution(attrs)
+        self.assertEqual(dist.get_platforms(),
+                         ['GNU/Linux', 'Some Evil Platform'])
+
+    def test_platforms_invalid_types(self):
+        attrs = {'name': 'Monty', 'version': '1.0',
+                 'platforms': ('GNU/Linux', 'Some Evil Platform')}
+        msg = "'platforms' should be a 'list', not 'tuple'"
+        with self.assertRaises(TypeError, msg=msg):
+            Distribution(attrs)
+
     def test_download_url(self):
         attrs = {'name': 'Boa', 'version': '3.0',
                  'download_url': 'http://example.org/boa'}
diff --git a/Misc/NEWS.d/next/Library/2017-11-23-16-15-55.bpo-19610.Dlca2P.rst b/Misc/NEWS.d/next/Library/2017-11-23-16-15-55.bpo-19610.Dlca2P.rst
new file mode 100644
index 00000000000..5ea87a45722
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-11-23-16-15-55.bpo-19610.Dlca2P.rst
@@ -0,0 +1,5 @@
+``setup()`` now raises :exc:`TypeError` for invalid types.
+
+The ``distutils.dist.Distribution`` class now explicitly raises an exception
+when ``classifiers``, ``keywords`` and ``platforms`` fields are not
+specified as a list.



More information about the Python-checkins mailing list