Skip to content

Commit

Permalink
Merge pull request #676 from groodt/groodt-metadata-23
Browse files Browse the repository at this point in the history
Implement Metadata 2.3
  • Loading branch information
takluyver committed Apr 30, 2024
2 parents 8ead22d + fcdfafd commit 6ba8c7e
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 7 deletions.
2 changes: 1 addition & 1 deletion flit/upload.py
Expand Up @@ -226,7 +226,7 @@ def build_post_data(action, metadata:Metadata):
"version": metadata.version,

# additional meta-data
"metadata_version": '2.1',
"metadata_version": '2.3',
"summary": metadata.summary,
"home_page": metadata.home_page,
"author": metadata.author,
Expand Down
37 changes: 31 additions & 6 deletions flit_core/flit_core/common.py
Expand Up @@ -347,8 +347,9 @@ class Metadata(object):
obsoletes_dist = ()
requires_external = ()
provides_extra = ()
dynamic = ()

metadata_version = "2.1"
metadata_version = "2.3"

def __init__(self, data):
data = data.copy()
Expand All @@ -359,9 +360,31 @@ def __init__(self, data):
assert hasattr(self, k), "data does not have attribute '{}'".format(k)
setattr(self, k, v)

def _normalise_name(self, n):
def _normalise_field_name(self, n):
return n.lower().replace('-', '_')

def _normalise_core_metadata_name(self, name):
# Normalized Names (PEP 503)
return re.sub(r"[-_.]+", "-", name).lower()

def _extract_extras(self, req):
match = re.search(r'\[([^]]*)\]', req)
if match:
list_str = match.group(1)
return [item.strip() for item in list_str.split(',')]
else:
return None

def _normalise_requires_dist(self, req):
extras = self._extract_extras(req)
if extras:
normalised_extras = [self._normalise_core_metadata_name(extra) for extra in extras]
normalised_extras_str = ', '.join(normalised_extras)
normalised_req = re.sub(r'\[([^]]*)\]', f"[{normalised_extras_str}]", req)
return normalised_req
else:
return req

def write_metadata_file(self, fp):
"""Write out metadata in the email headers format"""
fields = [
Expand All @@ -383,11 +406,11 @@ def write_metadata_file(self, fp):
]

for field in fields:
value = getattr(self, self._normalise_name(field))
value = getattr(self, self._normalise_field_name(field))
fp.write(u"{}: {}\n".format(field, value))

for field in optional_fields:
value = getattr(self, self._normalise_name(field))
value = getattr(self, self._normalise_field_name(field))
if value is not None:
# TODO: verify which fields can be multiline
# The spec has multiline examples for Author, Maintainer &
Expand All @@ -400,13 +423,15 @@ def write_metadata_file(self, fp):
fp.write(u'Classifier: {}\n'.format(clsfr))

for req in self.requires_dist:
fp.write(u'Requires-Dist: {}\n'.format(req))
normalised_req = self._normalise_requires_dist(req)
fp.write(u'Requires-Dist: {}\n'.format(normalised_req))

for url in self.project_urls:
fp.write(u'Project-URL: {}\n'.format(url))

for extra in self.provides_extra:
fp.write(u'Provides-Extra: {}\n'.format(extra))
normalised_extra = self._normalise_core_metadata_name(extra)
fp.write(u'Provides-Extra: {}\n'.format(normalised_extra))

if self.description is not None:
fp.write(u'\n' + self.description + u'\n')
Expand Down
49 changes: 49 additions & 0 deletions flit_core/flit_core/tests/test_common.py
Expand Up @@ -156,3 +156,52 @@ def test_metadata_multiline(tmp_path):
assert msg['Version'] == d['version']
assert [l.lstrip() for l in msg['Author'].splitlines()] == d['author'].splitlines()
assert not msg.defects

@pytest.mark.parametrize(
("requires_dist", "expected_result"),
[
('foo [extra_1, extra.2, extra-3, extra__4, extra..5, extra--6]', 'foo [extra-1, extra-2, extra-3, extra-4, extra-5, extra-6]'),
('foo', 'foo'),
('foo[bar]', 'foo[bar]'),
# https://packaging.python.org/en/latest/specifications/core-metadata/#requires-dist-multiple-use
('pkginfo', 'pkginfo'),
('zope.interface (>3.5.0)', 'zope.interface (>3.5.0)'),
("pywin32 >1.0; sys_platform == 'win32'", "pywin32 >1.0; sys_platform == 'win32'"),
],
)
def test_metadata_2_3_requires_dist(requires_dist, expected_result):
d = {
'name': 'foo',
'version': '1.0',
'requires_dist': [requires_dist],
}
md = Metadata(d)
sio = StringIO()
md.write_metadata_file(sio)
sio.seek(0)

msg = email.parser.Parser(policy=email.policy.compat32).parse(sio)
assert msg['Requires-Dist'] == expected_result
assert not msg.defects

@pytest.mark.parametrize(
("provides_extra", "expected_result"),
[
('foo', 'foo'),
('foo__bar..baz', 'foo-bar-baz'),
],
)
def test_metadata_2_3_provides_extra(provides_extra, expected_result):
d = {
'name': 'foo',
'version': '1.0',
'provides_extra': [provides_extra],
}
md = Metadata(d)
sio = StringIO()
md.write_metadata_file(sio)
sio.seek(0)

msg = email.parser.Parser(policy=email.policy.compat32).parse(sio)
assert msg['Provides-Extra'] == expected_result
assert not msg.defects

0 comments on commit 6ba8c7e

Please sign in to comment.