Skip to content

Commit 2ff10a0

Browse files
committed
feat: add validation for shortest import names to ensure parent packages are included
1 parent d4c5aeb commit 2ff10a0

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/poetry/core/factory.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,45 @@ def _validate_import_names(
345345
f"Import names found in both import-names and import-namespaces: {', '.join(duplicates)}"
346346
)
347347

348+
cls._validate_shortest_import_names(
349+
import_names, import_type="import-names", result=result
350+
)
351+
cls._validate_shortest_import_names(
352+
import_namespaces, import_type="import-namespaces", result=result
353+
)
354+
355+
@classmethod
356+
def _validate_shortest_import_names(
357+
cls,
358+
import_names: set[str],
359+
import_type: Literal["import-names", "import-namespaces"],
360+
result: dict[str, list[str]],
361+
) -> None:
362+
"""
363+
Validate that every import name includes its parent package names.
364+
365+
For each entry in `import_names`, ensure every parent package up to the
366+
top-level package is present in `import_names`. If a parent package is
367+
missing, a warning is appended to `result["warnings"]`. The
368+
`import_type` parameter is interpolated into the warning to indicate
369+
whether the context is `import-names` or `import-namespaces`.
370+
"""
371+
for import_name in import_names:
372+
if "." in import_name:
373+
parent: str | None = import_name.rsplit(".", maxsplit=1)[0]
374+
375+
while parent:
376+
if parent not in import_names:
377+
base = parent.split(".", maxsplit=1)[0]
378+
result["warnings"].append(
379+
f"Import name '{import_name}' should have all its parents up to '{base}' included in {import_type}."
380+
)
381+
break
382+
383+
parent = (
384+
parent.rsplit(".", maxsplit=1)[0] if "." in parent else None
385+
)
386+
348387
@classmethod
349388
def _configure_entry_points(
350389
cls,

tests/test_factory.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,44 @@ def test_create_poetry_raise_on_empty_import_namespaces(tmp_path: Path) -> None:
391391
_ = Factory().create_poetry(pyproject)
392392

393393

394+
def test_create_poetry_raise_warning_on_shortest_import_names_not_listed(
395+
caplog: LogCaptureFixture, tmp_path: Path
396+
) -> None:
397+
content = """
398+
[project]
399+
name = "my-package"
400+
version = "1.2.3"
401+
description = "Some description."
402+
requires-python = ">=3.6"
403+
404+
import-names = ["anotherpackage", "my_package.foo.bar", "foo.bar.baz"]
405+
import-namespaces = ["namespace.foo", "another_namespace"]
406+
407+
dependencies = []
408+
"""
409+
410+
pyproject = tmp_path / "pyproject.toml"
411+
pyproject.write_text(content)
412+
413+
_ = Factory().create_poetry(pyproject)
414+
415+
assert (
416+
"Import name 'foo.bar.baz' should have all its parents up to 'foo' included in import-names."
417+
in caplog.text
418+
)
419+
assert (
420+
"Import name 'my_package.foo.bar' should have all its parents up to 'my_package' included in import-names."
421+
in caplog.text
422+
)
423+
assert (
424+
"Import name 'namespace.foo' should have all its parents up to 'namespace' included in import-namespaces."
425+
in caplog.text
426+
)
427+
428+
assert "anotherpackage" not in caplog.text
429+
assert "another_namespace" not in caplog.text
430+
431+
394432
@pytest.mark.parametrize(
395433
"project", ["sample_project_with_groups", "sample_project_with_groups_new"]
396434
)

0 commit comments

Comments
 (0)