diff --git a/docs/cli.md b/docs/cli.md
index b91b637ebab..4e8e2ca1db4 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -465,7 +465,7 @@ poetry init
* `--name`: Name of the package.
* `--description`: Description of the package.
-* `--author`: Author of the package.
+* `--author`: Author of the package. Repeat the option to declare more than one author.
* `--python` Compatible Python versions.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
@@ -718,7 +718,7 @@ my-package
keep the [recommendations for a PyPI-friendly README](https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/)
in mind.
* `--description`: Description of the package.
-* `--author`: Author of the package.
+* `--author`: Author of the package. Repeat the option to declare more than one author.
* `--python` Compatible Python versions.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py
index e18e41d9253..c831a5a5129 100644
--- a/src/poetry/console/commands/init.py
+++ b/src/poetry/console/commands/init.py
@@ -39,7 +39,9 @@ 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 +150,27 @@ 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"):
+ authors = list(self.option("author"))
+ if not authors 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 is_interactive:
+ # Only the first author is asked for interactively, any further author
+ # given on the command line is kept as is.
+ default_author = authors[0] if authors else None
question = self.create_question(
- f"Author [{author}, n to skip]: ", default=author
+ f"Author [{default_author}, n to skip]: ",
+ default=default_author,
+ )
+ question.set_validator(
+ lambda v: self._validate_author(v, default_author or "")
)
- question.set_validator(lambda v: self._validate_author(v, author))
author = self.ask(question)
-
- authors = [author] if author else []
+ authors = [author, *authors[1:]] if author else authors[1:]
license_name = self.option("license")
if not license_name and is_interactive:
@@ -237,7 +245,7 @@ def _init_pyproject(
name,
version,
description=description,
- author=authors[0] if authors else None,
+ authors=authors,
readme_format=readme_format,
license=license_name,
python=python,
diff --git a/src/poetry/layouts/layout.py b/src/poetry/layouts/layout.py
index cbd406500a8..a4f5e63dfd2 100644
--- a/src/poetry/layouts/layout.py
+++ b/src/poetry/layouts/layout.py
@@ -20,6 +20,7 @@
if TYPE_CHECKING:
from collections.abc import Mapping
+ from collections.abc import Sequence
from tomlkit.items import InlineTable
from tomlkit.toml_document import TOMLDocument
@@ -65,7 +66,7 @@ def __init__(
version: str = "0.1.0",
description: str = "",
readme_format: str = "md",
- author: str | None = None,
+ authors: Sequence[str] | None = None,
license: str | None = None,
python: str | None = None,
dependencies: Mapping[str, str | Mapping[str, Any]] | None = None,
@@ -86,10 +87,7 @@ def __init__(
self._dependencies = dependencies or {}
self._dev_dependencies = dev_dependencies or {}
- if not author:
- author = "Your Name "
-
- self._author = author
+ self._authors = list(authors) if authors else ["Your Name "]
@property
def basedir(self) -> Path:
@@ -147,15 +145,16 @@ def generate_project_content(
project_content["name"] = self._project
project_content["version"] = self._version
project_content["description"] = self._description
- m = AUTHOR_REGEX.match(self._author)
- if m is None:
- # This should not happen because author has been validated before.
- raise ValueError(f"Invalid author: {self._author}")
- else:
- author = {"name": m.group("name")}
- if email := m.group("email"):
- author["email"] = email
- project_content["authors"].append(author)
+ for author_str in self._authors:
+ m = AUTHOR_REGEX.match(author_str)
+ if m is None:
+ # This should not happen because author has been validated before.
+ raise ValueError(f"Invalid author: {author_str}")
+ else:
+ author = {"name": m.group("name")}
+ if email := m.group("email"):
+ author["email"] = email
+ project_content["authors"].append(author)
if self._license:
project_content["license"] = self._license
diff --git a/tests/conftest.py b/tests/conftest.py
index e7ad8309183..2ed8fcd0964 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -487,7 +487,7 @@ def _factory(
layout("src")(
name,
"0.1.0",
- author="PyTest Tester ",
+ authors=["PyTest Tester "],
readme_format="md",
python=default_python,
dependencies=dependencies,
diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py
index 1e866d8dc8f..730ba73f7e5 100644
--- a/tests/console/commands/test_init.py
+++ b/tests/console/commands/test_init.py
@@ -922,6 +922,86 @@ def test_predefined_all_options(tester: CommandTester, repo: DummyRepository) ->
assert expected in output
+def test_multiple_authors(tester: CommandTester, source_dir: Path) -> None:
+ tester.execute(
+ "--name my-package "
+ "--author 'Foo Bar ' "
+ "--author 'Baz Qux ' "
+ "--python '>=3.8'",
+ interactive=False,
+ )
+
+ expected = """\
+authors = [
+ {name = "Foo Bar",email = "foo@example.com"},
+ {name = "Baz Qux",email = "baz@example.com"}
+]
+"""
+
+ assert expected in (source_dir / "pyproject.toml").read_text(encoding="utf-8")
+
+
+def test_multiple_authors_interactive_only_asks_for_the_first(
+ tester: CommandTester,
+) -> None:
+ inputs = [
+ "1.2.3", # Version
+ "", # Author, keeps the first one given on the command line
+ "n", # Interactive packages
+ "n", # Interactive dev packages
+ "\n", # Generate
+ ]
+
+ tester.execute(
+ "--name my-package "
+ "--description 'This is a description' "
+ "--author 'Foo Bar ' "
+ "--author 'Baz Qux ' "
+ "--python '>=3.8' "
+ "--license MIT",
+ inputs="\n".join(inputs),
+ )
+
+ expected = """\
+authors = [
+ {name = "Foo Bar",email = "foo@example.com"},
+ {name = "Baz Qux",email = "baz@example.com"}
+]
+"""
+
+ assert expected in tester.io.fetch_output()
+
+
+def test_multiple_authors_interactive_skipping_the_first_keeps_the_rest(
+ tester: CommandTester,
+) -> None:
+ inputs = [
+ "1.2.3", # Version
+ "n", # Author, skips the first one given on the command line
+ "n", # Interactive packages
+ "n", # Interactive dev packages
+ "\n", # Generate
+ ]
+
+ tester.execute(
+ "--name my-package "
+ "--description 'This is a description' "
+ "--author 'Foo Bar ' "
+ "--author 'Baz Qux ' "
+ "--python '>=3.8' "
+ "--license MIT",
+ inputs="\n".join(inputs),
+ )
+
+ expected = """\
+authors = [
+ {name = "Baz Qux",email = "baz@example.com"}
+]
+"""
+
+ assert expected in tester.io.fetch_output()
+
+
def test_add_package_with_extras_and_whitespace(tester: CommandTester) -> None:
command = tester.command
assert isinstance(command, InitCommand)