diff --git a/docs/cli.md b/docs/cli.md
index 4a950a9b2a5..06e00ad9c87 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -465,9 +465,9 @@ poetry init
* `--name`: Name of the package.
* `--description`: Description of the package.
-* `--author`: Author of the package.
+* `--author`: Author of the package. Can be specified multiple times.
* `--python` Compatible Python versions.
-* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
+* `--dependency`: Package to require with a version constraint. Should be in the format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
## install
@@ -717,9 +717,9 @@ 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. Can be specified multiple times.
* `--python` Compatible Python versions.
-* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
+* `--dependency`: Package to require with a version constraint. Should be in the format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--dependency`.
## publish
diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py
index a0921fdd1eb..3b56ce3cd55 100644
--- a/src/poetry/console/commands/init.py
+++ b/src/poetry/console/commands/init.py
@@ -39,7 +39,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. Can be specified multiple times.",
+ flag=False,
+ multiple=True,
+ ),
option("python", None, "Compatible Python versions.", flag=False),
option(
"dependency",
@@ -148,21 +154,28 @@ 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"]
+ provided_authors = self.option("author")
+ default_author = provided_authors[0] if provided_authors else None
+ if not default_author and vcs_config.get("user.name"):
+ default_author = vcs_config["user.name"]
author_email = vcs_config.get("user.email")
if author_email:
- author += f" <{author_email}>"
+ default_author += f" <{author_email}>"
- if is_interactive:
+ if is_interactive and len(provided_authors) < 2:
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, author))
- author = self.ask(question)
-
- authors = [author] if author else []
+ question.set_validator(
+ lambda v: self._validate_author(v, default_author or "")
+ )
+ selected_author = self.ask(question)
+ authors = [selected_author] if selected_author else []
+ else:
+ authors = list(provided_authors) if provided_authors else []
+ if not authors and default_author:
+ authors = [default_author]
license_name = self.option("license")
if not license_name and is_interactive:
@@ -237,7 +250,7 @@ def _init_pyproject(
name,
version,
description=description,
- author=authors[0] if authors else None,
+ authors=authors or None,
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..da34d766320 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
@@ -66,6 +67,7 @@ def __init__(
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 +88,9 @@ 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 or ([author] if author else []))
+ if not self._authors:
+ self._authors = ["Your Name "]
@property
def basedir(self) -> Path:
@@ -147,15 +148,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")}
+ for author in self._authors:
+ m = AUTHOR_REGEX.match(author)
+ if m is None:
+ # This should not happen because authors are validated before.
+ raise ValueError(f"Invalid author: {author}")
+
+ author_content = {"name": m.group("name")}
if email := m.group("email"):
- author["email"] = email
- project_content["authors"].append(author)
+ author_content["email"] = email
+ project_content["authors"].append(author_content)
if self._license:
project_content["license"] = self._license
diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py
index 935f6d1d9cb..561613efbda 100644
--- a/tests/console/commands/test_init.py
+++ b/tests/console/commands/test_init.py
@@ -17,6 +17,7 @@
from poetry.console.application import Application
from poetry.console.commands.init import InitCommand
from poetry.repositories import RepositoryPool
+from poetry.toml import TOMLFile
from tests.helpers import get_package
@@ -879,6 +880,24 @@ def test_predefined_all_options(tester: CommandTester, repo: DummyRepository) ->
assert expected in output
+def test_noninteractive_multiple_authors(
+ tester: CommandTester, source_dir: Path
+) -> None:
+ tester.execute(
+ "--name my-package "
+ "--author 'Foo Bar ' "
+ "--author 'Baz Qux '",
+ interactive=False,
+ )
+
+ pyproject_file = source_dir / "pyproject.toml"
+ authors = TOMLFile(pyproject_file).read()["project"]["authors"]
+ assert [dict(author) for author in authors] == [
+ {"name": "Foo Bar", "email": "foo@example.com"},
+ {"name": "Baz Qux", "email": "baz@example.com"},
+ ]
+
+
def test_add_package_with_extras_and_whitespace(tester: CommandTester) -> None:
command = tester.command
assert isinstance(command, InitCommand)
diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py
index 3fa72d5e0f9..3eb670ebfc4 100644
--- a/tests/console/commands/test_new.py
+++ b/tests/console/commands/test_new.py
@@ -10,6 +10,7 @@
from poetry.core.utils.helpers import module_name
from poetry.factory import Factory
+from poetry.toml import TOMLFile
if TYPE_CHECKING:
@@ -199,6 +200,23 @@ def test_command_new_with_readme(
assert project_section["readme"] == readme_file.name
+def test_command_new_multiple_authors(tester: CommandTester, tmp_path: Path) -> None:
+ path = tmp_path / "package"
+
+ tester.execute(
+ "--author 'Foo Bar ' "
+ "--author 'Baz Qux ' "
+ f"{path.as_posix()}"
+ )
+
+ pyproject_file = path / "pyproject.toml"
+ authors = TOMLFile(pyproject_file).read()["project"]["authors"]
+ assert [dict(author) for author in authors] == [
+ {"name": "Foo Bar", "email": "foo@example.com"},
+ {"name": "Baz Qux", "email": "baz@example.com"},
+ ]
+
+
@pytest.mark.parametrize(
["use_poetry_python", "python"],
[