Skip to content

Commit 838f0ee

Browse files
committed
Add option to compile bytecode during installation (similar to default behavior of old installer)
1 parent 7a8e6d5 commit 838f0ee

File tree

6 files changed

+72
-5
lines changed

6 files changed

+72
-5
lines changed

docs/cli.md

+16
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,21 @@ If you want to skip this installation, use the `--no-root` option.
225225
poetry install --no-root
226226
```
227227

228+
By default `poetry` does not compile Python source files to bytecode during installation.
229+
This speeds up the installation process, but the first execution may take a little more
230+
time because Python then compiles source files to bytecode automatically.
231+
If you want to compile source files to bytecode during installation,
232+
you can use the `--compile` option:
233+
234+
```bash
235+
poetry install --compile
236+
```
237+
238+
{{% note %}}
239+
The `--compile` option has no effect if `installer.modern-installation`
240+
is set to `false` because the old installer always compiles source files to bytecode.
241+
{{% /note %}}
242+
228243
### Options
229244

230245
* `--without`: The dependency groups to ignore.
@@ -236,6 +251,7 @@ poetry install --no-root
236251
* `--dry-run`: Output the operations but do not execute anything (implicitly enables --verbose).
237252
* `--extras (-E)`: Features to install (multiple values allowed).
238253
* `--all-extras`: Install all extra features (conflicts with --extras).
254+
* `--compile`: Compile Python source files to bytecode.
239255
* `--no-dev`: Do not install dev dependencies. (**Deprecated**, use `--without dev` or `--only main` instead)
240256
* `--remove-untracked`: Remove dependencies not presented in the lock file. (**Deprecated**, use `--sync` instead)
241257

src/poetry/console/commands/install.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@ class InstallCommand(InstallerCommand):
5454
multiple=True,
5555
),
5656
option("all-extras", None, "Install all extra dependencies."),
57+
option("only-root", None, "Exclude all dependencies."),
5758
option(
58-
"only-root",
59+
"compile",
5960
None,
60-
"Exclude all dependencies.",
61-
flag=True,
62-
multiple=False,
61+
(
62+
"Compile Python source files to bytecode."
63+
" (This option has no effect if modern-installation is disabled"
64+
" because the old installer always compiles.)"
65+
),
6366
),
6467
]
6568

@@ -146,6 +149,7 @@ def handle(self) -> int:
146149
self.installer.only_groups(self.activated_groups)
147150
self.installer.dry_run(self.option("dry-run"))
148151
self.installer.requires_synchronization(with_synchronization)
152+
self.installer.executor.enable_bytecode_compilation(self.option("compile"))
149153
self.installer.verbose(self.io.is_verbose())
150154

151155
return_code = self.installer.run()

src/poetry/installation/executor.py

+3
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ def verbose(self, verbose: bool = True) -> Executor:
123123

124124
return self
125125

126+
def enable_bytecode_compilation(self, enable: bool = True) -> None:
127+
self._wheel_installer.enable_bytecode_compilation(enable)
128+
126129
def pip_install(
127130
self, req: Path, upgrade: bool = False, editable: bool = False
128131
) -> int:

src/poetry/installation/wheel_installer.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ def for_source(self, source: WheelFile) -> WheelDestination:
6666
scheme_dict["headers"] = str(Path(scheme_dict["headers"]) / source.distribution)
6767

6868
return self.__class__(
69-
scheme_dict, interpreter=self.interpreter, script_kind=self.script_kind
69+
scheme_dict,
70+
interpreter=self.interpreter,
71+
script_kind=self.script_kind,
72+
bytecode_optimization_levels=self.bytecode_optimization_levels,
7073
)
7174

7275

@@ -90,6 +93,9 @@ def __init__(self, env: Env) -> None:
9093
schemes, interpreter=self._env.python, script_kind=script_kind
9194
)
9295

96+
def enable_bytecode_compilation(self, enable: bool = True) -> None:
97+
self._destination.bytecode_optimization_levels = (1,) if enable else ()
98+
9399
def install(self, wheel: Path) -> None:
94100
with WheelFile.open(Path(wheel.as_posix())) as source:
95101
install(

tests/console/commands/test_install.py

+18
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,24 @@ def test_sync_option_is_passed_to_the_installer(
162162
assert tester.command.installer._requires_synchronization
163163

164164

165+
@pytest.mark.parametrize("compile", [False, True])
166+
def test_compile_option_is_passed_to_the_installer(
167+
tester: CommandTester, mocker: MockerFixture, compile: bool
168+
):
169+
"""
170+
The --compile option is passed properly to the installer.
171+
"""
172+
mocker.patch.object(tester.command.installer, "run", return_value=1)
173+
enable_bytecode_compilation_mock = mocker.patch.object(
174+
tester.command.installer.executor._wheel_installer,
175+
"enable_bytecode_compilation",
176+
)
177+
178+
tester.execute("--compile" if compile else "")
179+
180+
enable_bytecode_compilation_mock.assert_called_once_with(compile)
181+
182+
165183
def test_no_all_extras_doesnt_populate_installer(
166184
tester: CommandTester, mocker: MockerFixture
167185
):

tests/installation/test_wheel_installer.py

+20
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,23 @@ def test_installer_file_contains_valid_version(default_installation: Path) -> No
5959
match = re.match(r"Poetry (?P<version>.*)", installer_content)
6060
assert match
6161
parse_constraint(match.group("version")) # must not raise an error
62+
63+
64+
def test_default_installation_no_bytecode(default_installation: Path) -> None:
65+
cache_dir = default_installation / "demo" / "__pycache__"
66+
assert not cache_dir.exists()
67+
68+
69+
@pytest.mark.parametrize("compile", [True, False])
70+
def test_enable_bytecode_compilation(
71+
env: MockEnv, demo_wheel: Path, compile: bool
72+
) -> None:
73+
installer = WheelInstaller(env)
74+
installer.enable_bytecode_compilation(compile)
75+
installer.install(demo_wheel)
76+
cache_dir = Path(env.paths["purelib"]) / "demo" / "__pycache__"
77+
if compile:
78+
assert cache_dir.exists()
79+
assert list(cache_dir.glob("*.pyc"))
80+
else:
81+
assert not cache_dir.exists()

0 commit comments

Comments
 (0)