diff --git a/Doc/distutils/setupscript.rst b/Doc/distutils/setupscript.rst --- a/Doc/distutils/setupscript.rst +++ b/Doc/distutils/setupscript.rst @@ -582,17 +582,17 @@ This information includes: | | package | | | +----------------------+---------------------------+-----------------+--------+ | ``long_description`` | longer description of the | long string | \(5) | | | package | | | +----------------------+---------------------------+-----------------+--------+ | ``download_url`` | location where the | URL | \(4) | | | package may be downloaded | | | +----------------------+---------------------------+-----------------+--------+ -| ``classifiers`` | a list of classifiers | list of strings | \(4) | +| ``classifiers`` | a list of classifiers | list of strings | (4)(7) | +----------------------+---------------------------+-----------------+--------+ | ``platforms`` | a list of platforms | list of strings | | +----------------------+---------------------------+-----------------+--------+ | ``license`` | license for the package | short string | \(6) | +----------------------+---------------------------+-----------------+--------+ Notes: @@ -603,31 +603,35 @@ Notes: It is recommended that versions take the form *major.minor[.patch[.sub]]*. (3) Either the author or the maintainer must be identified. If maintainer is 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 - `_. + versions prior to 2.2.3 or 2.3. (5) The ``long_description`` field is used by PyPI when you are :ref:`registering ` a package, to :ref:`build its home page `. (6) 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``. +(7) + This field must be a Python list. Otherwise, a :exc:`TypeError` is raised. + The valid classifiers are listed on `PyPI + `_. + 'short string' A single line of text, not more than 200 characters. 'long string' Multiple lines of plain text in reStructuredText format (see http://docutils.sourceforge.net/). 'list of strings' @@ -645,17 +649,17 @@ information is sometimes used to indicat (for final pre-release release testing). Some examples: 0.1.0 the first, experimental release of a package 1.0.1a2 the second alpha release of the first patch version of 1.0 -``classifiers`` are specified in a Python list:: +:option:`classifiers` are must be specified in a Python list:: setup(..., classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Environment :: Web Environment', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Developers', @@ -666,28 +670,19 @@ 1.0.1a2 'Operating System :: POSIX', 'Programming Language :: Python', 'Topic :: Communications :: Email', 'Topic :: Office/Business', 'Topic :: Software Development :: Bug Tracking', ], ) -If you wish to include classifiers in your :file:`setup.py` file and also wish -to remain backwards-compatible with Python releases prior to 2.2.3, then you can -include the following code fragment in your :file:`setup.py` before the -:func:`setup` call. :: - - # patch distutils if it can't cope with the "classifiers" or - # "download_url" keywords - from sys import version - if version < '2.2.3': - from distutils.dist import DistributionMetadata - DistributionMetadata.classifiers = None - DistributionMetadata.download_url = None +.. versionchanged:: 3.5 + :class:`~distutils.dist.Distribution` now raises a :exc:`TypeError` if + :option:`classifiers` are not specified in a Python list. .. _debug-setup-script: Debugging the setup script ========================== Sometimes things go wrong, and the setup script doesn't do what the developer diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py --- a/Lib/distutils/dist.py +++ b/Lib/distutils/dist.py @@ -1190,16 +1190,22 @@ class DistributionMetadata: return self.keywords or [] def get_platforms(self): return self.platforms or ["UNKNOWN"] 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 '%s'" + raise TypeError(msg % type(value).__name__) + self.classifiers = value + def get_download_url(self): return self.download_url or "UNKNOWN" # PEP 314 def get_requires(self): return self.requires or [] def set_requires(self, value): diff --git a/Lib/distutils/tests/test_dist.py b/Lib/distutils/tests/test_dist.py --- a/Lib/distutils/tests/test_dist.py +++ b/Lib/distutils/tests/test_dist.py @@ -335,16 +335,31 @@ class MetadataTestCase(support.TempdirMa def test_classifier(self): attrs = {'name': 'Boa', 'version': '3.0', 'classifiers': ['Programming Language :: Python :: 3']} dist = Distribution(attrs) meta = self.format_metadata(dist) self.assertIn('Metadata-Version: 1.1', meta) + def test_classifier_list(self): + attrs = {'name': 'Boa', 'version': '3.0', + 'classifiers': ['Programming Language :: Python :: 3']} + dist = Distribution(attrs) + self.assertEqual(dist.get_classifiers(), + ['Programming Language :: Python :: 3']) + + def test_classifier_tuple(self): + attrs = {'name': 'Boa', 'version': '3.0', + 'classifiers': ('Programming Language :: Python :: 3',)} + with self.assertRaises(TypeError) as cm: + Distribution(attrs) + self.assertEqual(str(cm.exception), + "classifiers should be a 'list', not 'tuple'") + def test_download_url(self): attrs = {'name': 'Boa', 'version': '3.0', 'download_url': 'http://example.org/boa'} dist = Distribution(attrs) meta = self.format_metadata(dist) self.assertIn('Metadata-Version: 1.1', meta) def test_long_description(self):