diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py
index a0921fdd1eb..5040dc86ceb 100644
--- a/src/poetry/console/commands/init.py
+++ b/src/poetry/console/commands/init.py
@@ -11,6 +11,8 @@
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
@@ -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,42 @@ 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
+ 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
)
- question.set_validator(lambda v: self._validate_author(v, author))
- author = self.ask(question)
+ if author
+ ]
+ authors: list[str] = []
+
+ 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:
+ 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 or ""))
+ author = self.ask(question)
+
+ authors = [author] if author else []
license_name = self.option("license")
if not license_name and is_interactive:
@@ -253,6 +282,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 +532,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 +547,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: