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 `. * *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.