From a74d584dc6360440b99efd7a7420d223a347b635 Mon Sep 17 00:00:00 2001 From: yann lei Date: Tue, 16 Jun 2026 23:22:17 +0930 Subject: [PATCH 1/4] feat: allow multiple init authors --- src/poetry/console/commands/init.py | 65 ++++++++++++++++++++++------- tests/console/commands/test_init.py | 27 ++++++++++++ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index a0921fdd1eb..7681da1fc39 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -11,10 +11,12 @@ from cleo.helpers import option from packaging.utils import canonicalize_name +from tomlkit import array from tomlkit import inline_table from poetry.console.commands.command import Command from poetry.console.commands.env_command import EnvCommand +from poetry.core.utils.patterns import AUTHOR_REGEX from poetry.utils.dependency_specification import RequirementsParser from poetry.utils.env.python import Python @@ -39,7 +41,13 @@ class InitCommand(Command): options: ClassVar[list[Option]] = [ option("name", None, "Name of the package.", flag=False), option("description", None, "Description of the package.", flag=False), - option("author", None, "Author name of the package.", flag=False), + option( + "author", + None, + "Author name of the package.", + flag=False, + multiple=True, + ), option("python", None, "Compatible Python versions.", flag=False), option( "dependency", @@ -148,21 +156,31 @@ def _init_pyproject( if not description and is_interactive: description = self.ask(self.create_question("Description []: ", default="")) - author = self.option("author") - if not author and vcs_config.get("user.name"): - author = vcs_config["user.name"] - author_email = vcs_config.get("user.email") - if author_email: - author += f" <{author_email}>" - - if is_interactive: - question = self.create_question( - f"Author [{author}, n to skip]: ", default=author + authors = [ + author + for author in ( + self._validate_author(author, "") for author in self.option("author") ) - question.set_validator(lambda v: self._validate_author(v, author)) - author = self.ask(question) + if author + ] + + if not authors: + author = None + if vcs_config.get("user.name"): + author = vcs_config["user.name"] + author_email = vcs_config.get("user.email") + if author_email: + author += f" <{author_email}>" - authors = [author] if author else [] + if is_interactive: + question = self.create_question( + f"Author [{author}, n to skip]: ", + default=author, + ) + question.set_validator(lambda v: self._validate_author(v, author)) + author = self.ask(question) + + authors = [author] if author else [] license_name = self.option("license") if not license_name and is_interactive: @@ -253,6 +271,11 @@ def _init_pyproject( layout_.create(project_path, with_pyproject=False) content = layout_.generate_project_content(project_path) + if len(authors) > 1: + content["project"]["authors"] = array().multiline(True) + for author in authors: + content["project"]["authors"].append(self._format_author(author)) + for section, item in content.items(): pyproject.data.append(section, item) @@ -498,7 +521,6 @@ def _format_requirements(self, requirements: list[dict[str, str]]) -> Requiremen @staticmethod def _validate_author(author: str, default: str) -> str | None: from poetry.core.utils.helpers import combine_unicode - from poetry.core.utils.patterns import AUTHOR_REGEX author = combine_unicode(author or default) @@ -514,6 +536,19 @@ def _validate_author(author: str, default: str) -> str | None: return author + @staticmethod + def _format_author(author: str) -> dict[str, str]: + m = AUTHOR_REGEX.match(author) + if m is None: + # This should not happen because author has been validated before. + raise ValueError(f"Invalid author: {author}") + + formatted_author = {"name": m.group("name")} + if email := m.group("email"): + formatted_author["email"] = email + + return formatted_author + @staticmethod def _validate_package(package: str | None) -> str | None: if package and len(package.split()) > 2: diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index 935f6d1d9cb..be6e221296f 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -972,6 +972,33 @@ def test_init_non_interactive_existing_pyproject_add_dependency( ) +def test_init_non_interactive_with_multiple_authors( + tester: CommandTester, source_dir: Path +) -> None: + tester.execute( + "--author 'Alice Example ' " + "--author 'Bob Example ' " + "--name 'my-package' " + "--python '>=3.12'", + interactive=False, + ) + + expected = """\ +[project] +name = "my-package" +version = "0.1.0" +description = "" +authors = [ + {name = "Alice Example",email = "alice@example.com"}, + {name = "Bob Example",email = "bob@example.com"}, +] +requires-python = ">=3.12" +dependencies = [ +] +""" + assert expected in (source_dir / "pyproject.toml").read_text(encoding="utf-8") + + def test_init_existing_pyproject_with_build_system_fails( tester: CommandTester, source_dir: Path, init_basic_inputs: str ) -> None: From 173f78f8b3d834f0bab5116a5e90fc5e9173e04d Mon Sep 17 00:00:00 2001 From: yann lei Date: Wed, 17 Jun 2026 14:58:26 +0930 Subject: [PATCH 2/4] Fix init author option typing --- src/poetry/console/commands/init.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 7681da1fc39..d6fbe366d9d 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import ClassVar +from typing import cast from cleo.helpers import option from packaging.utils import canonicalize_name @@ -156,10 +157,11 @@ def _init_pyproject( if not description and is_interactive: description = self.ask(self.create_question("Description []: ", default="")) + option_authors = cast("list[str]", self.option("author") or []) authors = [ author for author in ( - self._validate_author(author, "") for author in self.option("author") + self._validate_author(author, "") for author in option_authors ) if author ] @@ -177,7 +179,7 @@ def _init_pyproject( f"Author [{author}, n to skip]: ", default=author, ) - question.set_validator(lambda v: self._validate_author(v, author)) + question.set_validator(lambda v: self._validate_author(v, author or "")) author = self.ask(question) authors = [author] if author else [] From 8ba07e37b40c695eaf72110a841829704d530f98 Mon Sep 17 00:00:00 2001 From: yann lei Date: Wed, 17 Jun 2026 15:07:41 +0930 Subject: [PATCH 3/4] Fix init command import ordering --- src/poetry/console/commands/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index d6fbe366d9d..09b1925802d 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -12,12 +12,12 @@ from cleo.helpers import option from packaging.utils import canonicalize_name +from poetry.core.utils.patterns import AUTHOR_REGEX from tomlkit import array from tomlkit import inline_table from poetry.console.commands.command import Command from poetry.console.commands.env_command import EnvCommand -from poetry.core.utils.patterns import AUTHOR_REGEX from poetry.utils.dependency_specification import RequirementsParser from poetry.utils.env.python import Python From 434850aa99c8adc46d5584d7f19a8556aa33dfb9 Mon Sep 17 00:00:00 2001 From: yann lei Date: Wed, 17 Jun 2026 20:59:43 +0930 Subject: [PATCH 4/4] Preserve interactive author prompt --- src/poetry/console/commands/init.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 09b1925802d..5040dc86ceb 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING from typing import Any from typing import ClassVar -from typing import cast from cleo.helpers import option from packaging.utils import canonicalize_name @@ -157,18 +156,28 @@ def _init_pyproject( if not description and is_interactive: description = self.ask(self.create_question("Description []: ", default="")) - option_authors = cast("list[str]", self.option("author") or []) - authors = [ + raw_option_authors = self.option("author") + if raw_option_authors is None: + option_authors: list[str] = [] + elif isinstance(raw_option_authors, str): + option_authors = [raw_option_authors] + else: + option_authors = list(raw_option_authors) + + option_authors = [ author for author in ( self._validate_author(author, "") for author in option_authors ) if author ] + authors: list[str] = [] - if not authors: - author = None - if vcs_config.get("user.name"): + if len(option_authors) > 1 or (option_authors and not is_interactive): + authors = option_authors + else: + author = option_authors[0] if option_authors else None + if not author and vcs_config.get("user.name"): author = vcs_config["user.name"] author_email = vcs_config.get("user.email") if author_email: