Skip to content

Commit

Permalink
Merge pull request #138 from wimglenn/optional-metadata
Browse files Browse the repository at this point in the history
Optional fields are really optional
  • Loading branch information
jaraco committed May 9, 2022
2 parents 13bb77f + 5bac124 commit a7cfb56
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 92 deletions.
2 changes: 1 addition & 1 deletion distutils/ccompiler.py
Expand Up @@ -392,7 +392,7 @@ def _fix_compile_args(self, output_dir, macros, include_dirs):
return output_dir, macros, include_dirs

def _prep_compile(self, sources, output_dir, depends=None):
"""Decide which souce files must be recompiled.
"""Decide which source files must be recompiled.
Determine the list of object files corresponding to 'sources',
and figure out which ones really need to be recompiled.
Expand Down
6 changes: 1 addition & 5 deletions distutils/command/bdist_msi.py
Expand Up @@ -231,11 +231,7 @@ def run(self):
if os.path.exists(installer_name): os.unlink(installer_name)

metadata = self.distribution.metadata
author = metadata.author
if not author:
author = metadata.maintainer
if not author:
author = "UNKNOWN"
author = metadata.author or metadata.maintainer
version = metadata.get_version()
# ProductVersion must be strictly numeric
# XXX need to deal with prerelease versions
Expand Down
8 changes: 4 additions & 4 deletions distutils/command/bdist_rpm.py
Expand Up @@ -399,7 +399,7 @@ def _make_spec_file(self):
'%define unmangled_version ' + self.distribution.get_version(),
'%define release ' + self.release.replace('-','_'),
'',
'Summary: ' + self.distribution.get_description(),
'Summary: ' + (self.distribution.get_description() or "UNKNOWN"),
]

# Workaround for #14443 which affects some RPM based systems such as
Expand Down Expand Up @@ -438,7 +438,7 @@ def _make_spec_file(self):
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')

spec_file.extend([
'License: ' + self.distribution.get_license(),
'License: ' + (self.distribution.get_license() or "UNKNOWN"),
'Group: ' + self.group,
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
'Prefix: %{_prefix}', ])
Expand All @@ -464,7 +464,7 @@ def _make_spec_file(self):
spec_file.append('%s: %s' % (field, val))


if self.distribution.get_url() != 'UNKNOWN':
if self.distribution.get_url():
spec_file.append('Url: ' + self.distribution.get_url())

if self.distribution_name:
Expand All @@ -483,7 +483,7 @@ def _make_spec_file(self):
spec_file.extend([
'',
'%description',
self.distribution.get_long_description()
self.distribution.get_long_description() or "",
])

# put locale descriptions into spec file
Expand Down
43 changes: 4 additions & 39 deletions distutils/command/check.py
Expand Up @@ -82,54 +82,19 @@ def check_metadata(self):
"""Ensures that all required elements of meta-data are supplied.
Required fields:
name, version, URL
Recommended fields:
(author and author_email) or (maintainer and maintainer_email))
name, version
Warns if any are missing.
"""
metadata = self.distribution.metadata

missing = []
for attr in ('name', 'version', 'url'):
if not (hasattr(metadata, attr) and getattr(metadata, attr)):
for attr in 'name', 'version':
if not getattr(metadata, attr, None):
missing.append(attr)

if missing:
self.warn("missing required meta-data: %s" % ', '.join(missing))
if not (
self._check_contact("author", metadata) or
self._check_contact("maintainer", metadata)
):
self.warn("missing meta-data: either (author and author_email) " +
"or (maintainer and maintainer_email) " +
"should be supplied")

def _check_contact(self, kind, metadata):
"""
Returns True if the contact's name is specified and False otherwise.
This function will warn if the contact's email is not specified.
"""
name = getattr(metadata, kind) or ''
email = getattr(metadata, kind + '_email') or ''

msg = ("missing meta-data: if '{}' supplied, " +
"'{}' should be supplied too")

if name and email:
return True

if name:
self.warn(msg.format(kind, kind + '_email'))
return True

addresses = [(alias, addr) for alias, addr in getaddresses([email])]
if any(alias and addr for alias, addr in addresses):
# The contact's name can be encoded in the email: `Name <email>`
return True

return False
self.warn("missing required meta-data: %s" % ', '.join(missing))

def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
Expand Down
55 changes: 28 additions & 27 deletions distutils/dist.py
Expand Up @@ -1064,9 +1064,8 @@ def read_pkg_file(self, file):

def _read_field(name):
value = msg[name]
if value == 'UNKNOWN':
return None
return value
if value and value != "UNKNOWN":
return value

def _read_list(name):
values = msg.get_all(name, None)
Expand Down Expand Up @@ -1125,23 +1124,24 @@ def write_pkg_file(self, file):
self.classifiers or self.download_url):
version = '1.1'

# required fields
file.write('Metadata-Version: %s\n' % version)
file.write('Name: %s\n' % self.get_name())
file.write('Version: %s\n' % self.get_version())
file.write('Summary: %s\n' % self.get_description())
file.write('Home-page: %s\n' % self.get_url())
file.write('Author: %s\n' % self.get_contact())
file.write('Author-email: %s\n' % self.get_contact_email())
file.write('License: %s\n' % self.get_license())
if self.download_url:
file.write('Download-URL: %s\n' % self.download_url)

long_desc = rfc822_escape(self.get_long_description())
file.write('Description: %s\n' % long_desc)
def maybe_write(header, val):
if val:
file.write("{}: {}\n".format(header, val))

keywords = ','.join(self.get_keywords())
if keywords:
file.write('Keywords: %s\n' % keywords)
# optional fields
maybe_write("Summary", self.get_description())
maybe_write("Home-page", self.get_url())
maybe_write("Author", self.get_contact())
maybe_write("Author-email", self.get_contact_email())
maybe_write("License", self.get_license())
maybe_write("Download-URL", self.download_url)
maybe_write("Description", rfc822_escape(self.get_long_description() or ""))
maybe_write("Keywords", ",".join(self.get_keywords()))

self._write_list(file, 'Platform', self.get_platforms())
self._write_list(file, 'Classifier', self.get_classifiers())
Expand All @@ -1152,6 +1152,7 @@ def write_pkg_file(self, file):
self._write_list(file, 'Obsoletes', self.get_obsoletes())

def _write_list(self, file, name, values):
values = values or []
for value in values:
file.write('%s: %s\n' % (name, value))

Expand All @@ -1167,35 +1168,35 @@ def get_fullname(self):
return "%s-%s" % (self.get_name(), self.get_version())

def get_author(self):
return self.author or "UNKNOWN"
return self.author

def get_author_email(self):
return self.author_email or "UNKNOWN"
return self.author_email

def get_maintainer(self):
return self.maintainer or "UNKNOWN"
return self.maintainer

def get_maintainer_email(self):
return self.maintainer_email or "UNKNOWN"
return self.maintainer_email

def get_contact(self):
return self.maintainer or self.author or "UNKNOWN"
return self.maintainer or self.author

def get_contact_email(self):
return self.maintainer_email or self.author_email or "UNKNOWN"
return self.maintainer_email or self.author_email

def get_url(self):
return self.url or "UNKNOWN"
return self.url

def get_license(self):
return self.license or "UNKNOWN"
return self.license
get_licence = get_license

def get_description(self):
return self.description or "UNKNOWN"
return self.description

def get_long_description(self):
return self.long_description or "UNKNOWN"
return self.long_description

def get_keywords(self):
return self.keywords or []
Expand All @@ -1204,7 +1205,7 @@ def set_keywords(self, value):
self.keywords = _ensure_list(value, 'keywords')

def get_platforms(self):
return self.platforms or ["UNKNOWN"]
return self.platforms

def set_platforms(self, value):
self.platforms = _ensure_list(value, 'platforms')
Expand All @@ -1216,7 +1217,7 @@ def set_classifiers(self, value):
self.classifiers = _ensure_list(value, 'classifiers')

def get_download_url(self):
return self.download_url or "UNKNOWN"
return self.download_url

# PEP 314
def get_requires(self):
Expand Down
11 changes: 5 additions & 6 deletions distutils/tests/test_check.py
Expand Up @@ -43,7 +43,7 @@ def test_check_metadata(self):
# by default, check is checking the metadata
# should have some warnings
cmd = self._run()
self.assertEqual(cmd._warnings, 2)
self.assertEqual(cmd._warnings, 1)

# now let's add the required fields
# and run it again, to make sure we don't get
Expand Down Expand Up @@ -81,17 +81,16 @@ def test_check_author_maintainer(self):
cmd = self._run(metadata)
self.assertEqual(cmd._warnings, 0)

# the check should warn if only email is given and it does not
# contain the name
# the check should not warn if only email is given
metadata[kind + '_email'] = 'name@email.com'
cmd = self._run(metadata)
self.assertEqual(cmd._warnings, 1)
self.assertEqual(cmd._warnings, 0)

# the check should warn if only the name is given
# the check should not warn if only the name is given
metadata[kind] = "Name"
del metadata[kind + '_email']
cmd = self._run(metadata)
self.assertEqual(cmd._warnings, 1)
self.assertEqual(cmd._warnings, 0)

@unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils")
def test_check_document(self):
Expand Down
2 changes: 1 addition & 1 deletion distutils/tests/test_dist.py
Expand Up @@ -519,7 +519,7 @@ def test_read_metadata(self):
self.assertEqual(metadata.description, "xxx")
self.assertEqual(metadata.download_url, 'http://example.com')
self.assertEqual(metadata.keywords, ['one', 'two'])
self.assertEqual(metadata.platforms, ['UNKNOWN'])
self.assertEqual(metadata.platforms, None)
self.assertEqual(metadata.obsoletes, None)
self.assertEqual(metadata.requires, ['foo'])

Expand Down
4 changes: 2 additions & 2 deletions distutils/tests/test_register.py
Expand Up @@ -154,8 +154,8 @@ def _no_way(prompt=''):
req1 = dict(self.conn.reqs[0].headers)
req2 = dict(self.conn.reqs[1].headers)

self.assertEqual(req1['Content-length'], '1374')
self.assertEqual(req2['Content-length'], '1374')
self.assertEqual(req1['Content-length'], '1359')
self.assertEqual(req2['Content-length'], '1359')
self.assertIn(b'xxx', self.conn.reqs[1].data)

def test_password_not_in_file(self):
Expand Down
2 changes: 1 addition & 1 deletion distutils/tests/test_sdist.py
Expand Up @@ -251,7 +251,7 @@ def test_metadata_check_option(self):
cmd.run()
warnings = [msg for msg in self.get_logs(WARN) if
msg.startswith('warning: check:')]
self.assertEqual(len(warnings), 2)
self.assertEqual(len(warnings), 1)

# trying with a complete set of metadata
self.clear_logs()
Expand Down
4 changes: 1 addition & 3 deletions docs/distutils/examples.rst
Expand Up @@ -253,9 +253,7 @@ Running the ``check`` command will display some warnings:
$ python setup.py check
running check
warning: check: missing required meta-data: version, url
warning: check: missing meta-data: either (author and author_email) or
(maintainer and maintainer_email) should be supplied
warning: check: missing required meta-data: version
If you use the reStructuredText syntax in the ``long_description`` field and
Expand Down
6 changes: 3 additions & 3 deletions docs/distutils/setupscript.rst
Expand Up @@ -580,7 +580,7 @@ This information includes:
| ``maintainer_email`` | email address of the | email address | \(3) |
| | package maintainer | | |
+----------------------+---------------------------+-----------------+--------+
| ``url`` | home page for the package | URL | \(1) |
| ``url`` | home page for the package | URL | |
+----------------------+---------------------------+-----------------+--------+
| ``description`` | short, summary | short string | |
| | description of the | | |
Expand Down Expand Up @@ -610,8 +610,8 @@ 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`.
If maintainer is provided and author is not, distutils lists maintainer as
the author in :file:`PKG-INFO`.

(4)
The ``long_description`` field is used by PyPI when you publish a package,
Expand Down

0 comments on commit a7cfb56

Please sign in to comment.