Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dcaed6b

Browse files
authoredNov 23, 2017
bpo-19610: setup() now raises TypeError for invalid types (pythonGH-4519)
The Distribution class now explicitly raises an exception when 'classifiers', 'keywords' and 'platforms' fields are not specified as a list.
1 parent 6a54c67 commit dcaed6b

File tree

6 files changed

+112
-12
lines changed

6 files changed

+112
-12
lines changed
 

‎Doc/distutils/apiref.rst

+4
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ the full reference.
285285
See the :func:`setup` function for a list of keyword arguments accepted by the
286286
Distribution constructor. :func:`setup` creates a Distribution instance.
287287

288+
.. versionchanged:: 3.7
289+
:class:`~distutils.core.Distribution` now raises a :exc:`TypeError` if
290+
``classifiers``, ``keywords`` and ``platforms`` fields are not specified
291+
as a list.
288292

289293
.. class:: Command
290294

‎Doc/distutils/setupscript.rst

+27-12
Original file line numberDiff line numberDiff line change
@@ -581,17 +581,19 @@ This information includes:
581581
| | description of the | | |
582582
| | package | | |
583583
+----------------------+---------------------------+-----------------+--------+
584-
| ``long_description`` | longer description of the | long string | \(5) |
584+
| ``long_description`` | longer description of the | long string | \(4) |
585585
| | package | | |
586586
+----------------------+---------------------------+-----------------+--------+
587-
| ``download_url`` | location where the | URL | \(4) |
587+
| ``download_url`` | location where the | URL | |
588588
| | package may be downloaded | | |
589589
+----------------------+---------------------------+-----------------+--------+
590-
| ``classifiers`` | a list of classifiers | list of strings | \(4) |
590+
| ``classifiers`` | a list of classifiers | list of strings | (6)(7) |
591591
+----------------------+---------------------------+-----------------+--------+
592-
| ``platforms`` | a list of platforms | list of strings | |
592+
| ``platforms`` | a list of platforms | list of strings | (6)(8) |
593593
+----------------------+---------------------------+-----------------+--------+
594-
| ``license`` | license for the package | short string | \(6) |
594+
| ``keywords`` | a list of keywords | list of strings | (6)(8) |
595+
+----------------------+---------------------------+-----------------+--------+
596+
| ``license`` | license for the package | short string | \(5) |
595597
+----------------------+---------------------------+-----------------+--------+
596598

597599
Notes:
@@ -607,22 +609,30 @@ Notes:
607609
provided, distutils lists it as the author in :file:`PKG-INFO`.
608610

609611
(4)
610-
These fields should not be used if your package is to be compatible with Python
611-
versions prior to 2.2.3 or 2.3. The list is available from the `PyPI website
612-
<https://pypi.python.org/pypi>`_.
613-
614-
(5)
615612
The ``long_description`` field is used by PyPI when you are
616613
:ref:`registering <package-register>` a package, to
617614
:ref:`build its home page <package-display>`.
618615

619-
(6)
616+
(5)
620617
The ``license`` field is a text indicating the license covering the
621618
package where the license is not a selection from the "License" Trove
622619
classifiers. See the ``Classifier`` field. Notice that
623620
there's a ``licence`` distribution option which is deprecated but still
624621
acts as an alias for ``license``.
625622

623+
(6)
624+
This field must be a list.
625+
626+
(7)
627+
The valid classifiers are listed on
628+
`PyPI <http://pypi.python.org/pypi?:action=list_classifiers>`_.
629+
630+
(8)
631+
To preserve backward compatibility, this field also accepts a string. If
632+
you pass a comma-separated string ``'foo, bar'``, it will be converted to
633+
``['foo', 'bar']``, Otherwise, it will be converted to a list of one
634+
string.
635+
626636
'short string'
627637
A single line of text, not more than 200 characters.
628638

@@ -650,7 +660,7 @@ information is sometimes used to indicate sub-releases. These are
650660
1.0.1a2
651661
the second alpha release of the first patch version of 1.0
652662

653-
``classifiers`` are specified in a Python list::
663+
``classifiers`` must be specified in a list::
654664

655665
setup(...,
656666
classifiers=[
@@ -671,6 +681,11 @@ information is sometimes used to indicate sub-releases. These are
671681
],
672682
)
673683

684+
.. versionchanged:: 3.7
685+
:class:`~distutils.core.setup` now raises a :exc:`TypeError` if
686+
``classifiers``, ``keywords`` and ``platforms`` fields are not specified
687+
as a list.
688+
674689
.. _debug-setup-script:
675690

676691
Debugging the setup script

‎Doc/whatsnew/3.7.rst

+6
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,12 @@ README.rst is now included in the list of distutils standard READMEs and
298298
therefore included in source distributions.
299299
(Contributed by Ryan Gonzalez in :issue:`11913`.)
300300

301+
:class:`distutils.core.setup` now raises a :exc:`TypeError` if
302+
``classifiers``, ``keywords`` and ``platforms`` fields are not specified
303+
as a list. However, to minimize backwards incompatibility concerns,
304+
``keywords`` and ``platforms`` fields still accept a comma separated string.
305+
(Contributed by Berker Peksag in :issue:`19610`.)
306+
301307
http.client
302308
-----------
303309

‎Lib/distutils/dist.py

+26
Original file line numberDiff line numberDiff line change
@@ -1188,12 +1188,38 @@ def get_long_description(self):
11881188
def get_keywords(self):
11891189
return self.keywords or []
11901190

1191+
def set_keywords(self, value):
1192+
# If 'keywords' is a string, it will be converted to a list
1193+
# by Distribution.finalize_options(). To maintain backwards
1194+
# compatibility, do not raise an exception if 'keywords' is
1195+
# a string.
1196+
if not isinstance(value, (list, str)):
1197+
msg = "'keywords' should be a 'list', not %r"
1198+
raise TypeError(msg % type(value).__name__)
1199+
self.keywords = value
1200+
11911201
def get_platforms(self):
11921202
return self.platforms or ["UNKNOWN"]
11931203

1204+
def set_platforms(self, value):
1205+
# If 'platforms' is a string, it will be converted to a list
1206+
# by Distribution.finalize_options(). To maintain backwards
1207+
# compatibility, do not raise an exception if 'platforms' is
1208+
# a string.
1209+
if not isinstance(value, (list, str)):
1210+
msg = "'platforms' should be a 'list', not %r"
1211+
raise TypeError(msg % type(value).__name__)
1212+
self.platforms = value
1213+
11941214
def get_classifiers(self):
11951215
return self.classifiers or []
11961216

1217+
def set_classifiers(self, value):
1218+
if not isinstance(value, list):
1219+
msg = "'classifiers' should be a 'list', not %r"
1220+
raise TypeError(msg % type(value).__name__)
1221+
self.classifiers = value
1222+
11971223
def get_download_url(self):
11981224
return self.download_url or "UNKNOWN"
11991225

‎Lib/distutils/tests/test_dist.py

+44
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ def test_finalize_options(self):
195195
self.assertEqual(dist.metadata.platforms, ['one', 'two'])
196196
self.assertEqual(dist.metadata.keywords, ['one', 'two'])
197197

198+
attrs = {'keywords': 'foo bar',
199+
'platforms': 'foo bar'}
200+
dist = Distribution(attrs=attrs)
201+
dist.finalize_options()
202+
self.assertEqual(dist.metadata.platforms, ['foo bar'])
203+
self.assertEqual(dist.metadata.keywords, ['foo bar'])
204+
198205
def test_get_command_packages(self):
199206
dist = Distribution()
200207
self.assertEqual(dist.command_packages, None)
@@ -338,9 +345,46 @@ def test_classifier(self):
338345
attrs = {'name': 'Boa', 'version': '3.0',
339346
'classifiers': ['Programming Language :: Python :: 3']}
340347
dist = Distribution(attrs)
348+
self.assertEqual(dist.get_classifiers(),
349+
['Programming Language :: Python :: 3'])
341350
meta = self.format_metadata(dist)
342351
self.assertIn('Metadata-Version: 1.1', meta)
343352

353+
def test_classifier_invalid_type(self):
354+
attrs = {'name': 'Boa', 'version': '3.0',
355+
'classifiers': ('Programming Language :: Python :: 3',)}
356+
msg = "'classifiers' should be a 'list', not 'tuple'"
357+
with self.assertRaises(TypeError, msg=msg):
358+
Distribution(attrs)
359+
360+
def test_keywords(self):
361+
attrs = {'name': 'Monty', 'version': '1.0',
362+
'keywords': ['spam', 'eggs', 'life of brian']}
363+
dist = Distribution(attrs)
364+
self.assertEqual(dist.get_keywords(),
365+
['spam', 'eggs', 'life of brian'])
366+
367+
def test_keywords_invalid_type(self):
368+
attrs = {'name': 'Monty', 'version': '1.0',
369+
'keywords': ('spam', 'eggs', 'life of brian')}
370+
msg = "'keywords' should be a 'list', not 'tuple'"
371+
with self.assertRaises(TypeError, msg=msg):
372+
Distribution(attrs)
373+
374+
def test_platforms(self):
375+
attrs = {'name': 'Monty', 'version': '1.0',
376+
'platforms': ['GNU/Linux', 'Some Evil Platform']}
377+
dist = Distribution(attrs)
378+
self.assertEqual(dist.get_platforms(),
379+
['GNU/Linux', 'Some Evil Platform'])
380+
381+
def test_platforms_invalid_types(self):
382+
attrs = {'name': 'Monty', 'version': '1.0',
383+
'platforms': ('GNU/Linux', 'Some Evil Platform')}
384+
msg = "'platforms' should be a 'list', not 'tuple'"
385+
with self.assertRaises(TypeError, msg=msg):
386+
Distribution(attrs)
387+
344388
def test_download_url(self):
345389
attrs = {'name': 'Boa', 'version': '3.0',
346390
'download_url': 'http://example.org/boa'}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``setup()`` now raises :exc:`TypeError` for invalid types.
2+
3+
The ``distutils.dist.Distribution`` class now explicitly raises an exception
4+
when ``classifiers``, ``keywords`` and ``platforms`` fields are not
5+
specified as a list.

0 commit comments

Comments
 (0)