Skip to content

Commit f49f3ee

Browse files
authored
Introduce non-package-mode (#8650)
- metadata like `name` and `version` is not required - the root package is never installed (same as `--no-root`) - building and publishing is not possible
1 parent 5f75fdd commit f49f3ee

File tree

11 files changed

+135
-7
lines changed

11 files changed

+135
-7
lines changed

docs/basic-usage.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ cd pre-existing-project
7676
poetry init
7777
```
7878

79+
### Operating modes
80+
81+
Poetry can be operated in two different modes. The default mode is the **package mode**, which is the right mode
82+
if you want to package your project into an sdist or a wheel and perhaps publish it to a package index.
83+
In this mode, some metadata such as `name` and `version`, which are required for packaging, are mandatory.
84+
Further, the project itself will be installed in editable mode when running `poetry install`.
85+
86+
If you want to use Poetry only for dependency management but not for packaging, you can use the **non-package mode**:
87+
88+
```toml
89+
[tool.poetry]
90+
package-mode = false
91+
```
92+
93+
In this mode, metadata such as `name` and `version` are optional.
94+
Therefore, it is not possible to build a distribution or publish the project to a package index.
95+
Further, when running `poetry install`, Poetry does not try to install the project itself,
96+
but only its dependencies (same as `poetry install --no-root`).
97+
98+
{{% note %}}
99+
In the [pyproject section]({{< relref "pyproject" >}}) you can see which fields are required in package mode.
100+
{{% /note %}}
101+
79102
### Specifying dependencies
80103

81104
If you want to add dependencies to your project, you can specify them in the `tool.poetry.dependencies` section.

docs/pyproject.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,19 @@ menu:
1313

1414
The `tool.poetry` section of the `pyproject.toml` file is composed of multiple sections.
1515

16+
## package-mode
17+
18+
Whether Poetry operates in package mode (default) or not. **Optional**
19+
20+
See [basic usage]({{< relref "basic-usage#operating-modes" >}}) for more information.
21+
22+
```toml
23+
package-mode = false
24+
```
25+
1626
## name
1727

18-
The name of the package. **Required**
28+
The name of the package. **Required in package mode**
1929

2030
This should be a valid name as defined by [PEP 508](https://peps.python.org/pep-0508/#names).
2131

@@ -26,7 +36,7 @@ name = "my-package"
2636

2737
## version
2838

29-
The version of the package. **Required**
39+
The version of the package. **Required in package mode**
3040

3141
This should be a valid [PEP 440](https://peps.python.org/pep-0440/) string.
3242

@@ -43,7 +53,7 @@ If you would like to use semantic versioning for your project, please see
4353

4454
## description
4555

46-
A short description of the package. **Required**
56+
A short description of the package. **Required in package mode**
4757

4858
```toml
4959
description = "A short description of the package."
@@ -81,7 +91,7 @@ If your project is proprietary and does not use a specific licence, you can set
8191

8292
## authors
8393

84-
The authors of the package. **Required**
94+
The authors of the package. **Required in package mode**
8595

8696
This is a list of authors and should contain at least one author. Authors must be in the form `name <email>`.
8797

src/poetry/console/commands/build.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ def _build(
4949
builder(self.poetry, executable=executable).build(target_dir)
5050

5151
def handle(self) -> int:
52+
if not self.poetry.is_package_mode:
53+
self.line_error("Building a package is not possible in non-package mode.")
54+
return 1
55+
5256
with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env:
5357
fmt = self.option("format") or "all"
5458
dist_dir = Path(self.option("output"))

src/poetry/console/commands/install.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class InstallCommand(InstallerCommand):
7777
<info>--no-root</info> option like below:
7878
7979
<info> poetry install --no-root</info>
80+
81+
If you want to use Poetry only for dependency management but not for packaging,
82+
you can set the "package-mode" to false in your pyproject.toml file.
8083
"""
8184

8285
_loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
@@ -152,7 +155,7 @@ def handle(self) -> int:
152155
if return_code != 0:
153156
return return_code
154157

155-
if self.option("no-root"):
158+
if self.option("no-root") or not self.poetry.is_package_mode:
156159
return 0
157160

158161
log_install = (
@@ -184,9 +187,13 @@ def handle(self) -> int:
184187
# No need for an editable install in this case.
185188
self.line("")
186189
self.line_error(
187-
f"The current project could not be installed: <error>{e}</error>\n"
190+
f"Warning: The current project could not be installed: {e}\n"
188191
"If you do not want to install the current project"
189-
" use <c1>--no-root</c1>",
192+
" use <c1>--no-root</c1>.\n"
193+
"If you want to use Poetry only for dependency management"
194+
" but not for packaging, you can set the operating mode to "
195+
'"non-package" in your pyproject.toml file.\n'
196+
"In a future version of Poetry this warning will become an error!",
190197
style="warning",
191198
)
192199
return 0

src/poetry/console/commands/publish.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ class PublishCommand(Command):
5656
def handle(self) -> int:
5757
from poetry.publishing.publisher import Publisher
5858

59+
if not self.poetry.is_package_mode:
60+
self.line_error("Publishing a package is not possible in non-package mode.")
61+
return 1
62+
5963
dist_dir = self.option("dist-dir")
6064

6165
publisher = Publisher(self.poetry, self.io, Path(dist_dir))

tests/console/commands/test_build.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ def test_build_creates_packages_in_dist_directory_if_no_output_is_specified(
6262
assert all(archive.exists() for archive in build_artifacts)
6363

6464

65+
def test_build_not_possible_in_non_package_mode(
66+
fixture_dir: FixtureDirGetter,
67+
command_tester_factory: CommandTesterFactory,
68+
) -> None:
69+
source_dir = fixture_dir("non_package_mode")
70+
71+
poetry = Factory().create_poetry(source_dir)
72+
tester = command_tester_factory("build", poetry)
73+
74+
assert tester.execute() == 1
75+
assert (
76+
tester.io.fetch_error()
77+
== "Building a package is not possible in non-package mode.\n"
78+
)
79+
80+
6581
def test_build_with_multiple_readme_files(
6682
fixture_dir: FixtureDirGetter,
6783
tmp_path: Path,

tests/console/commands/test_check.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,24 @@ def test_check_private(
122122
assert tester.io.fetch_output() == expected
123123

124124

125+
def test_check_non_package_mode(
126+
mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter
127+
) -> None:
128+
mocker.patch(
129+
"poetry.poetry.Poetry.file",
130+
return_value=TOMLFile(fixture_dir("non_package_mode") / "pyproject.toml"),
131+
new_callable=mocker.PropertyMock,
132+
)
133+
134+
tester.execute()
135+
136+
expected = """\
137+
All set!
138+
"""
139+
140+
assert tester.io.fetch_output() == expected
141+
142+
125143
@pytest.mark.parametrize(
126144
("options", "expected", "expected_status"),
127145
[

tests/console/commands/test_install.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,20 @@ def test_install_missing_directory_dependency_with_no_directory(
506506
else:
507507
with pytest.raises(ValueError, match="does not exist"):
508508
tester.execute(options)
509+
510+
511+
def test_non_package_mode_does_not_try_to_install_root(
512+
command_tester_factory: CommandTesterFactory,
513+
project_factory: ProjectFactory,
514+
) -> None:
515+
content = """\
516+
[tool.poetry]
517+
package-mode = false
518+
"""
519+
poetry = project_factory(name="non-package-mode", pyproject_content=content)
520+
521+
tester = command_tester_factory("install", poetry=poetry)
522+
tester.execute()
523+
524+
assert tester.status_code == 0
525+
assert tester.io.fetch_error() == ""

tests/console/commands/test_publish.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@
2525
from tests.types import FixtureDirGetter
2626

2727

28+
def test_publish_not_possible_in_non_package_mode(
29+
fixture_dir: FixtureDirGetter,
30+
command_tester_factory: CommandTesterFactory,
31+
) -> None:
32+
source_dir = fixture_dir("non_package_mode")
33+
34+
poetry = Factory().create_poetry(source_dir)
35+
tester = command_tester_factory("publish", poetry)
36+
37+
assert tester.execute() == 1
38+
assert (
39+
tester.io.fetch_error()
40+
== "Publishing a package is not possible in non-package mode.\n"
41+
)
42+
43+
2844
def test_publish_returns_non_zero_code_for_upload_errors(
2945
app: PoetryTestApplication,
3046
app_tester: ApplicationTester,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[tool.poetry]
2+
package-mode = false
3+
4+
[tool.poetry.dependencies]
5+
python = "^3.8"
6+
cleo = "^0.6"
7+
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }

0 commit comments

Comments
 (0)