Skip to content

Commit

Permalink
Normalise extra names when reading config
Browse files Browse the repository at this point in the history
  • Loading branch information
takluyver committed Oct 27, 2024
1 parent 76e685d commit 19781c4
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 11 deletions.
12 changes: 6 additions & 6 deletions flit_core/flit_core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,6 @@ def __init__(self, data):
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:
Expand All @@ -385,7 +381,7 @@ def _extract_extras(self, req):
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 = [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
Expand Down Expand Up @@ -437,7 +433,7 @@ def write_metadata_file(self, fp):
fp.write(u'Project-URL: {}\n'.format(url))

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

if self.description is not None:
Expand All @@ -459,6 +455,10 @@ def make_metadata(module, ini_info):
return Metadata(md_dict)


def normalise_core_metadata_name(name):
"""Normalise a project or extra name (as in PEP 503, also PEP 685)"""
return re.sub(r"[-_.]+", "-", name).lower()


def normalize_dist_name(name: str, version: str) -> str:
"""Normalizes a name and a PEP 440 version
Expand Down
54 changes: 52 additions & 2 deletions flit_core/flit_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
except ImportError:
import tomli as tomllib

from .common import normalise_core_metadata_name
from .versionno import normalise_version

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -381,7 +382,31 @@ def _prep_metadata(md_sect, path):

# Move dev-requires into requires-extra
reqs_noextra = md_dict.pop('requires_dist', [])
res.reqs_by_extra = md_dict.pop('requires_extra', {})

reqs_extra = md_dict.pop('requires_extra', {})
extra_names_by_normed = {}
for e, reqs in reqs_extra.items():
if not all(isinstance(a, str) for a in reqs):
raise ConfigError(
f'Expected a string list for requires-extra group {e}'
)
if not name_is_valid(e):
raise ConfigError(
f'requires-extra group name {e!r} is not valid'
)
enorm = normalise_core_metadata_name(e)
extra_names_by_normed.setdefault(enorm, set()).add(e)
res.reqs_by_extra[enorm] = reqs

clashing_extra_names = [
g for g in extra_names_by_normed.values() if len(g) > 1
]
if clashing_extra_names:
fmted = ['/'.join(sorted(g)) for g in clashing_extra_names]
raise ConfigError(
f"requires-extra group names clash: {'; '.join(fmted)}"
)

dev_requires = md_dict.pop('dev_requires', None)
if dev_requires is not None:
if 'dev' in res.reqs_by_extra:
Expand Down Expand Up @@ -434,6 +459,8 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
if 'name' not in proj:
raise ConfigError('name must be specified in [project] table')
_check_type(proj, 'name', str)
if not name_is_valid(proj['name']):
raise ConfigError(f"name {proj['name']} is not valid")
md_dict['name'] = proj['name']
lc.module = md_dict['name'].replace('-', '_')

Expand Down Expand Up @@ -592,13 +619,29 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
raise ConfigError(
'Expected a dict of lists in optional-dependencies field'
)
extra_names_by_normed = {}
for e, reqs in optdeps.items():
if not all(isinstance(a, str) for a in reqs):
raise ConfigError(
'Expected a string list for optional-dependencies ({})'.format(e)
)
if not name_is_valid(e):
raise ConfigError(
f'optional-dependencies group name {e!r} is not valid'
)
enorm = normalise_core_metadata_name(e)
extra_names_by_normed.setdefault(enorm, set()).add(e)
lc.reqs_by_extra[enorm] = reqs

clashing_extra_names = [
g for g in extra_names_by_normed.values() if len(g) > 1
]
if clashing_extra_names:
fmted = ['/'.join(sorted(g)) for g in clashing_extra_names]
raise ConfigError(
f"optional-dependencies group names clash: {'; '.join(fmted)}"
)

lc.reqs_by_extra = optdeps.copy()
md_dict['provides_extra'] = sorted(lc.reqs_by_extra.keys())

md_dict['requires_dist'] = \
Expand Down Expand Up @@ -633,6 +676,13 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:

return lc


def name_is_valid(name) -> bool:
return bool(re.match(
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", name, re.IGNORECASE
))


def pep621_people(people, group_name='author') -> dict:
"""Convert authors/maintainers from PEP 621 to core metadata fields"""
names, emails = [], []
Expand Down
13 changes: 13 additions & 0 deletions flit_core/tests_core/samples/extras-newstyle.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[build-system]
requires = ["flit_core >=2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "module1"
version = "0.1"
description = "Example for testing"
dependencies = ["toml"]

[project.optional-dependencies]
test = ["pytest"]
cus__Tom = ["requests"] # To test normalisation
2 changes: 1 addition & 1 deletion flit_core/tests_core/samples/extras.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ requires = ["toml"]

[tool.flit.metadata.requires-extra]
test = ["pytest"]
custom = ["requests"]
cus__Tom = ["requests"] # To test normalisation
19 changes: 17 additions & 2 deletions flit_core/tests_core/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,20 @@ def test_extras():
assert requires_dist == {
'toml',
'pytest ; extra == "test"',
'requests ; extra == "custom"',
'requests ; extra == "cus-tom"',
}
assert set(info.metadata['provides_extra']) == {'test', 'custom'}
assert set(info.metadata['provides_extra']) == {'test', 'cus-tom'}

def test_extras_newstyle():
# As above, but with new-style [project] table
info = config.read_flit_config(samples_dir / 'extras-newstyle.toml')
requires_dist = set(info.metadata['requires_dist'])
assert requires_dist == {
'toml',
'pytest ; extra == "test"',
'requests ; extra == "cus-tom"',
}
assert set(info.metadata['provides_extra']) == {'test', 'cus-tom'}

def test_extras_dev_conflict():
with pytest.raises(config.ConfigError, match=r'dev-requires'):
Expand Down Expand Up @@ -141,6 +152,10 @@ def test_bad_include_paths(path, err_match):
({'dynamic': ['version']}, r'dynamic.*\[project\]'),
({'authors': ['thomas']}, r'author.*\bdict'),
({'maintainers': [{'title': 'Dr'}]}, r'maintainer.*title'),
({'name': 'mödule1'}, r'not valid'),
({'name': 'module1_'}, r'not valid'),
({'optional-dependencies': {'x_': []}}, r'not valid'),
({'optional-dependencies': {'x_a': [], 'X--a': []}}, r'clash'),
])
def test_bad_pep621_info(proj_bad, err_match):
proj = {'name': 'module1', 'version': '1.0', 'description': 'x'}
Expand Down

0 comments on commit 19781c4

Please sign in to comment.