Skip to content

Commit 70b2b0a

Browse files
authored
gh-129382: Match venv API symlink defaults to the CLI (#155759)
1 parent 94a75f7 commit 70b2b0a

4 files changed

Lines changed: 46 additions & 12 deletions

File tree

Doc/library/venv.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ mechanisms for third-party virtual environment creators to customize environment
302302
creation according to their needs, the :class:`EnvBuilder` class.
303303

304304
.. class:: EnvBuilder(system_site_packages=False, clear=False, \
305-
symlinks=False, upgrade=False, with_pip=False, \
305+
symlinks=None, upgrade=False, with_pip=False, \
306306
prompt=None, upgrade_deps=False, \
307307
*, scm_ignore_files=frozenset())
308308
@@ -316,7 +316,8 @@ creation according to their needs, the :class:`EnvBuilder` class.
316316
any existing target directory, before creating the environment.
317317

318318
* *symlinks* -- a boolean value indicating whether to attempt to symlink the
319-
Python binary rather than copying.
319+
Python binary rather than copying. If ``None``, the default is ``False`` on
320+
Windows and ``True`` on other platforms, matching the :ref:`CLI <venv-cli>`.
320321

321322
* *upgrade* -- a boolean value which, if true, will upgrade an existing
322323
environment with the running Python - for use when that Python has been
@@ -351,6 +352,9 @@ creation according to their needs, the :class:`EnvBuilder` class.
351352
.. versionchanged:: 3.13
352353
Added the ``scm_ignore_files`` parameter
353354

355+
.. versionchanged:: 3.16
356+
The default value of *symlinks* is now platform-dependent.
357+
354358
:class:`EnvBuilder` may be used as a base class.
355359

356360
.. method:: create(env_dir)
@@ -521,7 +525,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
521525
There is also a module-level convenience function:
522526

523527
.. function:: create(env_dir, system_site_packages=False, clear=False, \
524-
symlinks=False, with_pip=False, prompt=None, \
528+
symlinks=None, with_pip=False, prompt=None, \
525529
upgrade_deps=False, *, scm_ignore_files=frozenset())
526530
527531
Create an :class:`EnvBuilder` with the given keyword arguments, and call its
@@ -541,6 +545,9 @@ There is also a module-level convenience function:
541545
.. versionchanged:: 3.13
542546
Added the *scm_ignore_files* parameter
543547

548+
.. versionchanged:: 3.16
549+
The default value of *symlinks* is now platform-dependent.
550+
544551
An example of extending ``EnvBuilder``
545552
--------------------------------------
546553

Lib/test/test_venv.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,31 @@ def test_defaults_with_pathlike(self):
133133
self.run_with_capture(venv.create, FakePath(self.env_dir))
134134
self._check_output_of_default_create()
135135

136+
def test_envbuilder_symlinks(self):
137+
for kwargs, expected in (
138+
({}, os.name != 'nt'),
139+
({'symlinks': None}, os.name != 'nt'),
140+
({'symlinks': True}, True),
141+
({'symlinks': False}, False),
142+
):
143+
with self.subTest(kwargs=kwargs):
144+
builder = venv.EnvBuilder(**kwargs)
145+
self.assertIs(builder.symlinks, expected)
146+
147+
def test_create_symlinks(self):
148+
for kwargs, expected in (
149+
({}, os.name != 'nt'),
150+
({'symlinks': None}, os.name != 'nt'),
151+
({'symlinks': True}, True),
152+
({'symlinks': False}, False),
153+
):
154+
with self.subTest(kwargs=kwargs):
155+
with patch.object(venv.EnvBuilder, 'create', autospec=True) as create:
156+
venv.create(self.env_dir, **kwargs)
157+
builder, env_dir = create.call_args.args
158+
self.assertIs(builder.symlinks, expected)
159+
self.assertEqual(env_dir, self.env_dir)
160+
136161
def _check_output_of_default_create(self):
137162
self.isdir(self.bindir)
138163
self.isdir(self.include)
@@ -146,8 +171,7 @@ def _check_output_of_default_create(self):
146171
self.assertIn('home = %s' % path, data)
147172
self.assertIn('executable = %s' %
148173
os.path.realpath(sys.executable), data)
149-
copies = '' if os.name=='nt' else ' --copies'
150-
cmd = (f'command = {sys.executable} -m venv{copies} --without-pip '
174+
cmd = (f'command = {sys.executable} -m venv --without-pip '
151175
f'--without-scm-ignore-files {self.env_dir}')
152176
self.assertIn(cmd, data)
153177
fn = self.get_env_file(self.bindir, self.exe)
@@ -156,6 +180,7 @@ def _check_output_of_default_create(self):
156180
print('Contents of %r:' % bd)
157181
print(' %r' % os.listdir(bd))
158182
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
183+
self.assertEqual(os.path.islink(fn), os.name != 'nt' and can_symlink())
159184

160185
def test_config_file_command_key(self):
161186
options = [

Lib/venv/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,15 @@ class EnvBuilder:
2727
By default, the builder makes the system (global) site-packages dir
2828
*un*available to the created environment.
2929
30-
If invoked using the Python -m option, the default is to use copying
31-
on Windows platforms but symlinks elsewhere. If instantiated some
32-
other way, the default is to *not* use symlinks.
30+
By default, the builder uses copying on Windows platforms but symlinks
31+
elsewhere, matching the behaviour when invoked using the Python -m option.
3332
3433
:param system_site_packages: If True, the system (global) site-packages
3534
dir is available to created environments.
3635
:param clear: If True, delete the contents of the environment directory if
3736
it already exists, before environment creation.
3837
:param symlinks: If True, attempt to symlink rather than copy files into
39-
virtual environment.
38+
virtual environment. If None, use the platform default.
4039
:param upgrade: If True, upgrade an existing virtual environment.
4140
:param with_pip: If True, ensure pip is installed in the virtual
4241
environment
@@ -47,11 +46,11 @@ class EnvBuilder:
4746
"""
4847

4948
def __init__(self, system_site_packages=False, clear=False,
50-
symlinks=False, upgrade=False, with_pip=False, prompt=None,
49+
symlinks=None, upgrade=False, with_pip=False, prompt=None,
5150
upgrade_deps=False, *, scm_ignore_files=frozenset()):
5251
self.system_site_packages = system_site_packages
5352
self.clear = clear
54-
self.symlinks = symlinks
53+
self.symlinks = os.name != 'nt' if symlinks is None else symlinks
5554
self.upgrade = upgrade
5655
self.with_pip = with_pip
5756
self.orig_prompt = prompt
@@ -607,7 +606,7 @@ def upgrade_dependencies(self, context):
607606

608607

609608
def create(env_dir, system_site_packages=False, clear=False,
610-
symlinks=False, with_pip=False, prompt=None, upgrade_deps=False,
609+
symlinks=None, with_pip=False, prompt=None, upgrade_deps=False,
611610
*, scm_ignore_files=frozenset()):
612611
"""Create a virtual environment in a directory."""
613612
builder = EnvBuilder(system_site_packages=system_site_packages,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The :class:`venv.EnvBuilder` and :func:`venv.create` APIs now use the
2+
platform-dependent default for symlinks from :mod:`venv`'s command-line
3+
interface.

0 commit comments

Comments
 (0)