diff --git a/docs/basic-usage.md b/docs/basic-usage.md index b9c0a0d7c41..c5f61fdfd68 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -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. diff --git a/docs/pyproject.md b/docs/pyproject.md index 1a496f5cedb..cde59b92cb7 100644 --- a/docs/pyproject.md +++ b/docs/pyproject.md @@ -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). @@ -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. @@ -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." @@ -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 `. diff --git a/src/poetry/console/commands/build.py b/src/poetry/console/commands/build.py index 86626fcf64a..07ca01762ba 100644 --- a/src/poetry/console/commands/build.py +++ b/src/poetry/console/commands/build.py @@ -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")) diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index 8e716caa2b0..8186719cf8d 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -77,6 +77,9 @@ class InstallCommand(InstallerCommand): --no-root option like below: poetry install --no-root + +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"] @@ -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 = ( @@ -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: {e}\n" + f"Warning: The current project could not be installed: {e}\n" "If you do not want to install the current project" - " use --no-root", + " use --no-root.\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 diff --git a/src/poetry/console/commands/publish.py b/src/poetry/console/commands/publish.py index 18d4cb0f456..2671c7a11d1 100644 --- a/src/poetry/console/commands/publish.py +++ b/src/poetry/console/commands/publish.py @@ -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)) diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index 023e855ee59..80fe49c1c58 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -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, diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index 7957385d958..777015b9480 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -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"), [ diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index 633a8113a16..2692fa59f88 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -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() == "" diff --git a/tests/console/commands/test_publish.py b/tests/console/commands/test_publish.py index e123a082c9a..78216936409 100644 --- a/tests/console/commands/test_publish.py +++ b/tests/console/commands/test_publish.py @@ -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, diff --git a/tests/fixtures/non_package_mode/pyproject.toml b/tests/fixtures/non_package_mode/pyproject.toml new file mode 100644 index 00000000000..95a5edb17f7 --- /dev/null +++ b/tests/fixtures/non_package_mode/pyproject.toml @@ -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" } diff --git a/tests/test_factory.py b/tests/test_factory.py index d9e49ce8a68..5cc433bb3c0 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -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: