Skip to content

Commit beced2e

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

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/poetry/core/factory.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,61 @@ 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_names,
350+
import_namespaces=import_namespaces,
351+
result=result,
352+
)
353+
354+
@classmethod
355+
def _validate_shortest_import_names(
356+
cls,
357+
import_names: set[str],
358+
import_namespaces: set[str],
359+
result: dict[str, list[str]],
360+
) -> None:
361+
"""
362+
Ensure import names and namespaces include their parent packages.
363+
364+
For entries in `import-names`, any parent package may be listed in
365+
either `import-names` or `import-namespaces`. For entries in
366+
`import-namespaces`, any parent package must be listed in
367+
`import-namespaces` only.
368+
"""
369+
for import_name in import_names:
370+
if "." in import_name:
371+
parent: str | None = import_name.rsplit(".", maxsplit=1)[0]
372+
373+
while parent:
374+
if parent not in import_names and parent not in import_namespaces:
375+
base = parent.split(".", maxsplit=1)[0]
376+
result["warnings"].append(
377+
f"Import name '{import_name}' should have all its parents up to '{base}'"
378+
f" included in import-names or import-namespaces."
379+
)
380+
break
381+
382+
parent = (
383+
parent.rsplit(".", maxsplit=1)[0] if "." in parent else None
384+
)
385+
386+
for import_namespace in import_namespaces:
387+
if "." in import_namespace:
388+
parent = import_namespace.rsplit(".", maxsplit=1)[0]
389+
390+
while parent:
391+
if parent not in import_namespaces:
392+
base = parent.split(".", maxsplit=1)[0]
393+
result["warnings"].append(
394+
f"Import namespace '{import_namespace}' should have all its parents up to '{base}'"
395+
f" included in import-namespaces."
396+
)
397+
break
398+
399+
parent = (
400+
parent.rsplit(".", maxsplit=1)[0] if "." in parent else None
401+
)
402+
348403
@classmethod
349404
def _configure_entry_points(
350405
cls,

tests/test_factory.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,43 @@ 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"]
405+
import-namespaces = ["foo", "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 'my_package.foo.bar' should have all its parents up to 'my_package' included in import-names or import-namespaces."
417+
in caplog.text
418+
)
419+
assert (
420+
"Import namespace 'namespace.foo' should have all its parents up to 'namespace' included in import-namespaces."
421+
in caplog.text
422+
)
423+
424+
assert "'anotherpackage'" not in caplog.text
425+
assert "'foo.bar'" not in caplog.text
426+
427+
assert "'foo'" not in caplog.text
428+
assert "'another_namespace'" not in caplog.text
429+
430+
394431
@pytest.mark.parametrize(
395432
"project", ["sample_project_with_groups", "sample_project_with_groups_new"]
396433
)

0 commit comments

Comments
 (0)