diff --git a/docs/cli.md b/docs/cli.md
index b71a3949646..3624d09d5db 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -213,6 +213,12 @@ poetry install --no-root
Installation of your project's package is also skipped when the `--only`
option is used.
+If you do not want to install the current project in [editable](https://pip.pypa.io/en/stable/cli/pip_install/#install-editable) mode, run the `install` command with the `--no-editable` flag:
+
+```bash
+poetry install --no-editable
+```
+
### Options
* `--without`: The dependency groups to ignore for installation.
@@ -223,6 +229,7 @@ option is used.
* `--no-root`: Do not install the root package (your project).
* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose).
* `--extras (-E)`: Features to install (multiple values allowed).
+* `--no-editable`: Do not install the root package in editable mode.
* `--no-dev`: Do not install dev dependencies. (**Deprecated**)
* `--dev-only`: Only install dev dependencies. (**Deprecated**)
* `--remove-untracked`: Remove dependencies not presented in the lock file. (**Deprecated**)
diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py
index c150e1bd203..669f7a7e5af 100644
--- a/src/poetry/console/commands/install.py
+++ b/src/poetry/console/commands/install.py
@@ -70,6 +70,12 @@ class InstallCommand(InstallerCommand):
flag=False,
multiple=True,
),
+ option(
+ "--no-editable",
+ None,
+ "Do not install the root package in editable mode.",
+ flag=True,
+ ),
]
help = """The install command reads the poetry.lock> file from
@@ -89,9 +95,11 @@ class InstallCommand(InstallerCommand):
_loggers = ["poetry.repositories.pypi_repository", "poetry.inspection.info"]
def handle(self) -> int:
+ from poetry.core.masonry.builders.sdist import SdistBuilder
from poetry.core.masonry.utils.module import ModuleOrPackageNotFound
from poetry.masonry.builders import EditableBuilder
+ from poetry.utils.pip import pip_install
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
@@ -174,13 +182,17 @@ def handle(self) -> int:
if self.option("no-root") or self.option("only"):
return 0
- try:
- builder = EditableBuilder(self.poetry, self._env, self._io)
- except ModuleOrPackageNotFound:
- # This is likely due to the fact that the project is an application
- # not following the structure expected by Poetry
- # If this is a true error it will be picked up later by build anyway.
- return 0
+ if self.option("no-editable"):
+ builder = SdistBuilder(self.poetry)
+ else:
+ try:
+ builder = EditableBuilder(self.poetry, self._env, self._io)
+ except ModuleOrPackageNotFound:
+ # This is likely due to the fact that the project is an
+ # application not following the structure expected by Poetry
+ # If this is a true error it will be picked up later by build
+ # anyway.
+ return 0
log_install = (
"Installing> the current project:"
@@ -197,7 +209,16 @@ def handle(self) -> int:
self.line("")
return 0
- builder.build()
+ if self.option("no-editable"):
+ with builder.setup_py():
+ pip_install(
+ path=self.poetry.package.root_dir,
+ environment=self._env,
+ deps=False,
+ upgrade=True,
+ )
+ else:
+ builder.build()
if overwrite:
self.overwrite(log_install.format(tag="success"))
diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py
index 485207d5274..4d59e649c7c 100644
--- a/tests/console/commands/test_install.py
+++ b/tests/console/commands/test_install.py
@@ -41,3 +41,22 @@ def test_sync_option_is_passed_to_the_installer(
tester.execute("--sync")
assert tester.command.installer._requires_synchronization
+
+
+def test_no_editable_option(tester: "CommandTester", mocker: "MockerFixture"):
+ mocker.patch.object(tester.command.installer, "run", return_value=0)
+ pip_install_mock = mocker.patch("poetry.utils.pip.pip_install")
+ builder_mock = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder")
+ editable_builder_mock = mocker.patch("poetry.masonry.builders.EditableBuilder")
+
+ tester.execute("--no-editable")
+
+ pip_install_mock.assert_called_once_with(
+ path=tester.command.poetry.package.root_dir,
+ environment=tester.command.env,
+ deps=False,
+ upgrade=True,
+ )
+ builder_mock.assert_called_once_with(tester.command.poetry)
+ builder_mock.return_value.setup_py.assert_called_once()
+ editable_builder_mock.assert_not_called()