Skip to content

Commit 8bd9308

Browse files
authored
Allow dash-separated module name in pyproject.toml (#4566)
2 parents 57379cd + 0aac59e commit 8bd9308

File tree

6 files changed

+79
-33
lines changed

6 files changed

+79
-33
lines changed

setuptools/config/_validate_pyproject/error_reporting.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import typing
77
from contextlib import contextmanager
88
from textwrap import indent, wrap
9-
from typing import Any, Dict, Generator, Iterator, List, Optional, Sequence, Union, cast
9+
from typing import Any, Dict, Generator, Iterator, List, Optional, Sequence, Union
1010

1111
from .fastjsonschema_exceptions import JsonSchemaValueException
1212

@@ -316,9 +316,7 @@ def _label(self, path: Sequence[str]) -> str:
316316
def _value(self, value: Any, path: Sequence[str]) -> str:
317317
if path[-1] == "type" and not self._is_property(path):
318318
type_ = self._jargon(value)
319-
return (
320-
f"[{', '.join(type_)}]" if isinstance(value, list) else cast(str, type_)
321-
)
319+
return f"[{', '.join(type_)}]" if isinstance(type_, list) else type_
322320
return repr(value)
323321

324322
def _inline_attrs(self, schema: dict, path: Sequence[str]) -> Iterator[str]:

setuptools/config/_validate_pyproject/fastjsonschema_validations.py

+24-24
Large diffs are not rendered by default.

setuptools/config/_validate_pyproject/formats.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def pep508_identifier(name: str) -> bool:
8383
from packaging import requirements as _req
8484
except ImportError: # pragma: no cover
8585
# let's try setuptools vendored version
86-
from setuptools._vendor.packaging import requirements as _req # type: ignore
86+
from setuptools._vendor.packaging import ( # type: ignore[no-redef]
87+
requirements as _req,
88+
)
8789

8890
def pep508(value: str) -> bool:
8991
"""See :ref:`PyPA's dependency specifiers <pypa:dependency-specifiers>`
@@ -289,6 +291,25 @@ def python_module_name(value: str) -> bool:
289291
return python_qualified_identifier(value)
290292

291293

294+
def python_module_name_relaxed(value: str) -> bool:
295+
"""Similar to :obj:`python_module_name`, but relaxed to also accept
296+
dash characters (``-``) and cover special cases like ``pip-run``.
297+
298+
It is recommended, however, that beginners avoid dash characters,
299+
as they require advanced knowledge about Python internals.
300+
301+
The following are disallowed:
302+
303+
* names starting/ending in dashes,
304+
* names ending in ``-stubs`` (potentially collide with :obj:`pep561_stub_name`).
305+
"""
306+
if value.startswith("-") or value.endswith("-"):
307+
return False
308+
if value.endswith("-stubs"):
309+
return False # Avoid collision with PEP 561
310+
return python_module_name(value.replace("-", "_"))
311+
312+
292313
def python_entrypoint_group(value: str) -> bool:
293314
"""See ``Data model > group`` in the :ref:`PyPA's entry-points specification
294315
<pypa:entry-points>`.

setuptools/config/setuptools.schema.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,14 @@
148148
},
149149
"namespace-packages": {
150150
"type": "array",
151-
"items": {"type": "string", "format": "python-module-name"},
151+
"items": {"type": "string", "format": "python-module-name-relaxed"},
152152
"$comment": "https://setuptools.pypa.io/en/latest/userguide/package_discovery.html",
153153
"description": "**DEPRECATED**: use implicit namespaces instead (:pep:`420`)."
154154
},
155155
"py-modules": {
156156
"description": "Modules that setuptools will manipulate",
157157
"type": "array",
158-
"items": {"type": "string", "format": "python-module-name"},
158+
"items": {"type": "string", "format": "python-module-name-relaxed"},
159159
"$comment": "TODO: clarify the relationship with ``packages``"
160160
},
161161
"data-files": {
@@ -250,7 +250,7 @@
250250
"description": "Valid package name (importable or :pep:`561`).",
251251
"type": "string",
252252
"anyOf": [
253-
{"type": "string", "format": "python-module-name"},
253+
{"type": "string", "format": "python-module-name-relaxed"},
254254
{"type": "string", "format": "pep561-stub-name"}
255255
]
256256
},

setuptools/tests/config/test_apply_pyprojecttoml.py

+27
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,33 @@ def test_default_patterns(self, tmp_path):
297297
assert set(dist.metadata.license_files) == {*license_files, "LICENSE.txt"}
298298

299299

300+
class TestPyModules:
301+
# https://github.com/pypa/setuptools/issues/4316
302+
303+
def dist(self, name):
304+
toml_config = f"""
305+
[project]
306+
name = "test"
307+
version = "42.0"
308+
[tool.setuptools]
309+
py-modules = [{name!r}]
310+
"""
311+
pyproject = Path("pyproject.toml")
312+
pyproject.write_text(cleandoc(toml_config), encoding="utf-8")
313+
return pyprojecttoml.apply_configuration(Distribution({}), pyproject)
314+
315+
@pytest.mark.parametrize("module", ["pip-run", "abc-d.λ-xyz-e"])
316+
def test_valid_module_name(self, tmp_path, monkeypatch, module):
317+
monkeypatch.chdir(tmp_path)
318+
assert module in self.dist(module).py_modules
319+
320+
@pytest.mark.parametrize("module", ["pip run", "-pip-run", "pip-run-stubs"])
321+
def test_invalid_module_name(self, tmp_path, monkeypatch, module):
322+
monkeypatch.chdir(tmp_path)
323+
with pytest.raises(ValueError, match="py-modules"):
324+
self.dist(module).py_modules
325+
326+
300327
class TestDeprecatedFields:
301328
def test_namespace_packages(self, tmp_path):
302329
pyproject = tmp_path / "pyproject.toml"

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ commands =
8484
[testenv:generate-validation-code]
8585
skip_install = True
8686
deps =
87-
validate-pyproject[all]==0.18
87+
validate-pyproject[all]==0.19
8888
commands =
8989
python -m tools.generate_validation_code
9090

0 commit comments

Comments
 (0)