Skip to content

Commit

Permalink
Introduce non-package-mode (#8650)
Browse files Browse the repository at this point in the history
- metadata like `name` and `version` is not required
- the root package is never installed (same as `--no-root`)
- building and publishing is not possible
  • Loading branch information
radoering authored Feb 13, 2024
1 parent 5f75fdd commit f49f3ee
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 7 deletions.
23 changes: 23 additions & 0 deletions docs/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ cd pre-existing-project
poetry init
```

### Operating modes

Poetry can be operated in two different modes. The default mode is the **package mode**, which is the right mode
if you want to package your project into an sdist or a wheel and perhaps publish it to a package index.
In this mode, some metadata such as `name` and `version`, which are required for packaging, are mandatory.
Further, the project itself will be installed in editable mode when running `poetry install`.

If you want to use Poetry only for dependency management but not for packaging, you can use the **non-package mode**:

```toml
[tool.poetry]
package-mode = false
```

In this mode, metadata such as `name` and `version` are optional.
Therefore, it is not possible to build a distribution or publish the project to a package index.
Further, when running `poetry install`, Poetry does not try to install the project itself,
but only its dependencies (same as `poetry install --no-root`).

{{% note %}}
In the [pyproject section]({{< relref "pyproject" >}}) you can see which fields are required in package mode.
{{% /note %}}

### Specifying dependencies

If you want to add dependencies to your project, you can specify them in the `tool.poetry.dependencies` section.
Expand Down
18 changes: 14 additions & 4 deletions docs/pyproject.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ menu:

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

## package-mode

Whether Poetry operates in package mode (default) or not. **Optional**

See [basic usage]({{< relref "basic-usage#operating-modes" >}}) for more information.

```toml
package-mode = false
```

## name

The name of the package. **Required**
The name of the package. **Required in package mode**

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

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

## version

The version of the package. **Required**
The version of the package. **Required in package mode**

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

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

## description

A short description of the package. **Required**
A short description of the package. **Required in package mode**

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

## authors

The authors of the package. **Required**
The authors of the package. **Required in package mode**

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

Expand Down
4 changes: 4 additions & 0 deletions src/poetry/console/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def _build(
builder(self.poetry, executable=executable).build(target_dir)

def handle(self) -> int:
if not self.poetry.is_package_mode:
self.line_error("Building a package is not possible in non-package mode.")
return 1

with build_environment(poetry=self.poetry, env=self.env, io=self.io) as env:
fmt = self.option("format") or "all"
dist_dir = Path(self.option("output"))
Expand Down
13 changes: 10 additions & 3 deletions src/poetry/console/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class InstallCommand(InstallerCommand):
<info>--no-root</info> option like below:
<info> poetry install --no-root</info>
If you want to use Poetry only for dependency management but not for packaging,
you can set the "package-mode" to false in your pyproject.toml file.
"""

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

if self.option("no-root"):
if self.option("no-root") or not self.poetry.is_package_mode:
return 0

log_install = (
Expand Down Expand Up @@ -184,9 +187,13 @@ def handle(self) -> int:
# No need for an editable install in this case.
self.line("")
self.line_error(
f"The current project could not be installed: <error>{e}</error>\n"
f"Warning: The current project could not be installed: {e}\n"
"If you do not want to install the current project"
" use <c1>--no-root</c1>",
" use <c1>--no-root</c1>.\n"
"If you want to use Poetry only for dependency management"
" but not for packaging, you can set the operating mode to "
'"non-package" in your pyproject.toml file.\n'
"In a future version of Poetry this warning will become an error!",
style="warning",
)
return 0
Expand Down
4 changes: 4 additions & 0 deletions src/poetry/console/commands/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class PublishCommand(Command):
def handle(self) -> int:
from poetry.publishing.publisher import Publisher

if not self.poetry.is_package_mode:
self.line_error("Publishing a package is not possible in non-package mode.")
return 1

dist_dir = self.option("dist-dir")

publisher = Publisher(self.poetry, self.io, Path(dist_dir))
Expand Down
16 changes: 16 additions & 0 deletions tests/console/commands/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ def test_build_creates_packages_in_dist_directory_if_no_output_is_specified(
assert all(archive.exists() for archive in build_artifacts)


def test_build_not_possible_in_non_package_mode(
fixture_dir: FixtureDirGetter,
command_tester_factory: CommandTesterFactory,
) -> None:
source_dir = fixture_dir("non_package_mode")

poetry = Factory().create_poetry(source_dir)
tester = command_tester_factory("build", poetry)

assert tester.execute() == 1
assert (
tester.io.fetch_error()
== "Building a package is not possible in non-package mode.\n"
)


def test_build_with_multiple_readme_files(
fixture_dir: FixtureDirGetter,
tmp_path: Path,
Expand Down
18 changes: 18 additions & 0 deletions tests/console/commands/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,24 @@ def test_check_private(
assert tester.io.fetch_output() == expected


def test_check_non_package_mode(
mocker: MockerFixture, tester: CommandTester, fixture_dir: FixtureDirGetter
) -> None:
mocker.patch(
"poetry.poetry.Poetry.file",
return_value=TOMLFile(fixture_dir("non_package_mode") / "pyproject.toml"),
new_callable=mocker.PropertyMock,
)

tester.execute()

expected = """\
All set!
"""

assert tester.io.fetch_output() == expected


@pytest.mark.parametrize(
("options", "expected", "expected_status"),
[
Expand Down
17 changes: 17 additions & 0 deletions tests/console/commands/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,20 @@ def test_install_missing_directory_dependency_with_no_directory(
else:
with pytest.raises(ValueError, match="does not exist"):
tester.execute(options)


def test_non_package_mode_does_not_try_to_install_root(
command_tester_factory: CommandTesterFactory,
project_factory: ProjectFactory,
) -> None:
content = """\
[tool.poetry]
package-mode = false
"""
poetry = project_factory(name="non-package-mode", pyproject_content=content)

tester = command_tester_factory("install", poetry=poetry)
tester.execute()

assert tester.status_code == 0
assert tester.io.fetch_error() == ""
16 changes: 16 additions & 0 deletions tests/console/commands/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@
from tests.types import FixtureDirGetter


def test_publish_not_possible_in_non_package_mode(
fixture_dir: FixtureDirGetter,
command_tester_factory: CommandTesterFactory,
) -> None:
source_dir = fixture_dir("non_package_mode")

poetry = Factory().create_poetry(source_dir)
tester = command_tester_factory("publish", poetry)

assert tester.execute() == 1
assert (
tester.io.fetch_error()
== "Publishing a package is not possible in non-package mode.\n"
)


def test_publish_returns_non_zero_code_for_upload_errors(
app: PoetryTestApplication,
app_tester: ApplicationTester,
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/non_package_mode/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool.poetry]
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
6 changes: 6 additions & 0 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ def test_create_poetry_with_multi_constraints_dependency(
assert len(package.requires) == 2


def test_create_poetry_non_package_mode(fixture_dir: FixtureDirGetter) -> None:
poetry = Factory().create_poetry(fixture_dir("non_package_mode"))

assert not poetry.is_package_mode


def test_poetry_with_default_source_legacy(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
Expand Down

0 comments on commit f49f3ee

Please sign in to comment.