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]

Reply via email to