Skip to content

Commit f471ed3

Browse files
Allow dotted namespace package names in flit init
Validate module names by ensuring each dot-separated component is a valid identifier, allowing namespace packages like 'my_org.my_pkg'. Closes #679.
1 parent 547d3fe commit f471ed3

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

flit/init.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ def validate_email(self, s):
6969
def validate_homepage(self, s):
7070
return not s or s.startswith(('http://', 'https://'))
7171

72+
def validate_module_name(self, s):
73+
return bool(s) and all(n.isidentifier() for n in s.split('.'))
74+
7275
def guess_module_name(self):
7376
packages, modules = [], []
7477
for p in self.directory.iterdir():
@@ -178,7 +181,7 @@ def initialise(self):
178181
return
179182

180183
module = self.prompt_text('Module name', self.guess_module_name(),
181-
str.isidentifier)
184+
self.validate_module_name)
182185
author = self.prompt_text('Author', self.defaults.get('author'),
183186
lambda s: True)
184187
author_email = self.prompt_text('Author email',

tests/test_init.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,73 @@ def test_init_non_ascii_author_name():
255255
with license.open(encoding='utf-8') as f:
256256
license_text = f.read()
257257
assert "Test Authôr" in license_text
258+
259+
260+
@pytest.mark.parametrize('name', [
261+
'my_package',
262+
'foo',
263+
'my_org.my_pkg',
264+
'a.b.c',
265+
'_pkg',
266+
'pkg_123',
267+
'a1.b2.c3',
268+
])
269+
def test_validate_module_name_valid(name):
270+
ib = init.IniterBase()
271+
assert ib.validate_module_name(name) is True
272+
273+
274+
@pytest.mark.parametrize('name', [
275+
'',
276+
'123',
277+
'a..b',
278+
'a.',
279+
'.a',
280+
'a-b',
281+
'foo bar',
282+
'foo/bar',
283+
'foo@bar',
284+
'foo.123',
285+
'123.foo',
286+
])
287+
def test_validate_module_name_invalid(name):
288+
ib = init.IniterBase()
289+
assert ib.validate_module_name(name) is False
290+
291+
292+
def test_init_dotted_module_name():
293+
responses = ['my_org.my_pkg', # Module name
294+
'Test Author', # Author
295+
'test@example.com', # Author email
296+
'', # Home page omitted
297+
'4', # Skip license
298+
]
299+
with TemporaryDirectory() as td, \
300+
patch_data_dir(), \
301+
faking_input(responses):
302+
ti = init.TerminalIniter(td)
303+
ti.initialise()
304+
305+
generated = Path(td) / 'pyproject.toml'
306+
assert_isfile(generated)
307+
with generated.open('rb') as f:
308+
data = tomllib.load(f)
309+
assert data['project']['name'] == 'my_org.my_pkg'
310+
311+
312+
def test_init_module_name_validator():
313+
responses = ['invalid-module-name', # fails validation
314+
'my_org.my_pkg', # passes validation
315+
'Test Author',
316+
'test@example.com',
317+
'',
318+
'4',
319+
]
320+
with TemporaryDirectory() as td, \
321+
patch_data_dir(), \
322+
faking_input(responses):
323+
ti = init.TerminalIniter(td)
324+
ti.initialise()
325+
with Path(td, 'pyproject.toml').open('rb') as f:
326+
data = tomllib.load(f)
327+
assert data['project']['name'] == 'my_org.my_pkg'

0 commit comments

Comments
 (0)