https://github.com/python/cpython/commit/70b2b0a2dcce71e8b94757b8ed9313b0cbe0fc4f
commit: 70b2b0a2dcce71e8b94757b8ed9313b0cbe0fc4f
branch: main
author: Zanie Blue <[email protected]>
committer: hauntsaninja <[email protected]>
date: 2026-08-15T21:47:16-07:00
summary:
gh-129382: Match venv API symlink defaults to the CLI (#155759)
files:
A Misc/NEWS.d/next/Library/2025-01-31-02-27-53.gh-issue-129382.GI93CM.rst
M Doc/library/venv.rst
M Lib/test/test_venv.py
M Lib/venv/__init__.py
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
index 1ad2401fe2f3f31..fd9c9b9a19dd9b3 100644
--- a/Doc/library/venv.rst
+++ b/Doc/library/venv.rst
@@ -302,7 +302,7 @@ mechanisms for third-party virtual environment creators to
customize environment
creation according to their needs, the :class:`EnvBuilder` class.
.. class:: EnvBuilder(system_site_packages=False, clear=False, \
- symlinks=False, upgrade=False, with_pip=False, \
+ symlinks=None, upgrade=False, with_pip=False, \
prompt=None, upgrade_deps=False, \
*, scm_ignore_files=frozenset())
@@ -316,7 +316,8 @@ creation according to their needs, the :class:`EnvBuilder`
class.
any existing target directory, before creating the environment.
* *symlinks* -- a boolean value indicating whether to attempt to symlink
the
- Python binary rather than copying.
+ Python binary rather than copying. If ``None``, the default is ``False``
on
+ Windows and ``True`` on other platforms, matching the :ref:`CLI
<venv-cli>`.
* *upgrade* -- a boolean value which, if true, will upgrade an existing
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.
.. versionchanged:: 3.13
Added the ``scm_ignore_files`` parameter
+ .. versionchanged:: 3.16
+ The default value of *symlinks* is now platform-dependent.
+
:class:`EnvBuilder` may be used as a base class.
.. method:: create(env_dir)
@@ -521,7 +525,7 @@ creation according to their needs, the :class:`EnvBuilder`
class.
There is also a module-level convenience function:
.. function:: create(env_dir, system_site_packages=False, clear=False, \
- symlinks=False, with_pip=False, prompt=None, \
+ symlinks=None, with_pip=False, prompt=None, \
upgrade_deps=False, *, scm_ignore_files=frozenset())
Create an :class:`EnvBuilder` with the given keyword arguments, and call
its
@@ -541,6 +545,9 @@ There is also a module-level convenience function:
.. versionchanged:: 3.13
Added the *scm_ignore_files* parameter
+ .. versionchanged:: 3.16
+ The default value of *symlinks* is now platform-dependent.
+
An example of extending ``EnvBuilder``
--------------------------------------
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index e98e52c2ea20453..b4ad1bf3f412948 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -133,6 +133,31 @@ def test_defaults_with_pathlike(self):
self.run_with_capture(venv.create, FakePath(self.env_dir))
self._check_output_of_default_create()
+ def test_envbuilder_symlinks(self):
+ for kwargs, expected in (
+ ({}, os.name != 'nt'),
+ ({'symlinks': None}, os.name != 'nt'),
+ ({'symlinks': True}, True),
+ ({'symlinks': False}, False),
+ ):
+ with self.subTest(kwargs=kwargs):
+ builder = venv.EnvBuilder(**kwargs)
+ self.assertIs(builder.symlinks, expected)
+
+ def test_create_symlinks(self):
+ for kwargs, expected in (
+ ({}, os.name != 'nt'),
+ ({'symlinks': None}, os.name != 'nt'),
+ ({'symlinks': True}, True),
+ ({'symlinks': False}, False),
+ ):
+ with self.subTest(kwargs=kwargs):
+ with patch.object(venv.EnvBuilder, 'create', autospec=True) as
create:
+ venv.create(self.env_dir, **kwargs)
+ builder, env_dir = create.call_args.args
+ self.assertIs(builder.symlinks, expected)
+ self.assertEqual(env_dir, self.env_dir)
+
def _check_output_of_default_create(self):
self.isdir(self.bindir)
self.isdir(self.include)
@@ -146,8 +171,7 @@ def _check_output_of_default_create(self):
self.assertIn('home = %s' % path, data)
self.assertIn('executable = %s' %
os.path.realpath(sys.executable), data)
- copies = '' if os.name=='nt' else ' --copies'
- cmd = (f'command = {sys.executable} -m venv{copies} --without-pip '
+ cmd = (f'command = {sys.executable} -m venv --without-pip '
f'--without-scm-ignore-files {self.env_dir}')
self.assertIn(cmd, data)
fn = self.get_env_file(self.bindir, self.exe)
@@ -156,6 +180,7 @@ def _check_output_of_default_create(self):
print('Contents of %r:' % bd)
print(' %r' % os.listdir(bd))
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
+ self.assertEqual(os.path.islink(fn), os.name != 'nt' and can_symlink())
def test_config_file_command_key(self):
options = [
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
index bd2762d55ef6961..4c8e4e8efeaa724 100644
--- a/Lib/venv/__init__.py
+++ b/Lib/venv/__init__.py
@@ -27,16 +27,15 @@ class EnvBuilder:
By default, the builder makes the system (global) site-packages dir
*un*available to the created environment.
- If invoked using the Python -m option, the default is to use copying
- on Windows platforms but symlinks elsewhere. If instantiated some
- other way, the default is to *not* use symlinks.
+ By default, the builder uses copying on Windows platforms but symlinks
+ elsewhere, matching the behaviour when invoked using the Python -m option.
:param system_site_packages: If True, the system (global) site-packages
dir is available to created environments.
:param clear: If True, delete the contents of the environment directory if
it already exists, before environment creation.
:param symlinks: If True, attempt to symlink rather than copy files into
- virtual environment.
+ virtual environment. If None, use the platform default.
:param upgrade: If True, upgrade an existing virtual environment.
:param with_pip: If True, ensure pip is installed in the virtual
environment
@@ -47,11 +46,11 @@ class EnvBuilder:
"""
def __init__(self, system_site_packages=False, clear=False,
- symlinks=False, upgrade=False, with_pip=False, prompt=None,
+ symlinks=None, upgrade=False, with_pip=False, prompt=None,
upgrade_deps=False, *, scm_ignore_files=frozenset()):
self.system_site_packages = system_site_packages
self.clear = clear
- self.symlinks = symlinks
+ self.symlinks = os.name != 'nt' if symlinks is None else symlinks
self.upgrade = upgrade
self.with_pip = with_pip
self.orig_prompt = prompt
@@ -607,7 +606,7 @@ def upgrade_dependencies(self, context):
def create(env_dir, system_site_packages=False, clear=False,
- symlinks=False, with_pip=False, prompt=None, upgrade_deps=False,
+ symlinks=None, with_pip=False, prompt=None, upgrade_deps=False,
*, scm_ignore_files=frozenset()):
"""Create a virtual environment in a directory."""
builder = EnvBuilder(system_site_packages=system_site_packages,
diff --git
a/Misc/NEWS.d/next/Library/2025-01-31-02-27-53.gh-issue-129382.GI93CM.rst
b/Misc/NEWS.d/next/Library/2025-01-31-02-27-53.gh-issue-129382.GI93CM.rst
new file mode 100644
index 000000000000000..b1b704cc6f86537
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-31-02-27-53.gh-issue-129382.GI93CM.rst
@@ -0,0 +1,3 @@
+The :class:`venv.EnvBuilder` and :func:`venv.create` APIs now use the
+platform-dependent default for symlinks from :mod:`venv`'s command-line
+interface.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]