Skip to content

Commit

Permalink
Switch to scm_ignore_file
Browse files Browse the repository at this point in the history
  • Loading branch information
brettcannon committed Sep 4, 2023
1 parent 41e541c commit ce17684
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 89 deletions.
24 changes: 18 additions & 6 deletions Doc/library/venv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ creation according to their needs, the :class:`EnvBuilder` class.

.. class:: EnvBuilder(system_site_packages=False, clear=False, \
symlinks=False, upgrade=False, with_pip=False, \
prompt=None, upgrade_deps=False, *, gitignore=False)
prompt=None, upgrade_deps=False, *, scm_ignore_file=None)
The :class:`EnvBuilder` class accepts the following keyword arguments on
instantiation:
Expand Down Expand Up @@ -172,9 +172,11 @@ creation according to their needs, the :class:`EnvBuilder` class.

* ``upgrade_deps`` -- Update the base venv modules to the latest on PyPI

* ``gitignore`` -- a Boolean value which, if true, will create a
``.gitignore`` file in the target directory, containing ``*`` to have the
environment ignored by git.
* ``scm_ignore_file`` -- Create an ignore file for the specified source
control manager (SCM). Support is defined by having a method named
``create_{scm}_ignore_file``. The only value currently supported is
``"git"`` via :meth:`create_git_ignore_file`.


.. versionchanged:: 3.4
Added the ``with_pip`` parameter
Expand All @@ -186,7 +188,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
Added the ``upgrade_deps`` parameter

.. versionadded:: 3.13
Added the ``gitignore`` parameter
Added the ``scm_ignore_file`` parameter

Creators of third-party virtual environment tools will be free to use the
provided :class:`EnvBuilder` class as a base class.
Expand Down Expand Up @@ -346,11 +348,18 @@ creation according to their needs, the :class:`EnvBuilder` class.
The directories are allowed to exist (for when an existing environment
is being upgraded).

.. method:: create_git_ignore_file(context)

Creates a ``.gitignore`` file within the virtual environment that causes
the entire directory to be ignored by the ``git`` source control manager.

.. versionadded:: 3.13

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, \
upgrade_deps=False, *, gitignore=False)
upgrade_deps=False, *, scm_ignore_file=None)
Create an :class:`EnvBuilder` with the given keyword arguments, and call its
:meth:`~EnvBuilder.create` method with the *env_dir* argument.
Expand All @@ -366,6 +375,9 @@ There is also a module-level convenience function:
.. versionchanged:: 3.9
Added the ``upgrade_deps`` parameter

.. versionchanged:: 3.13
Added the ``scm_ignore_file`` parameter

An example of extending ``EnvBuilder``
--------------------------------------

Expand Down
73 changes: 42 additions & 31 deletions Doc/using/venv-create.inc
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,48 @@ your :ref:`Python installation <using-on-windows>`::
The command, if run with ``-h``, will show the available options::
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
[--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade-deps]
ENV_DIR [ENV_DIR ...]
Creates virtual Python environments in one or more target directories.
positional arguments:
ENV_DIR A directory to create the environment in.
optional arguments:
-h, --help show this help message and exit
--system-site-packages
Give the virtual environment access to the system
site-packages dir.
--symlinks Try to use symlinks rather than copies, when symlinks
are not the default for the platform.
--copies Try to use copies rather than symlinks, even when
symlinks are the default for the platform.
--clear Delete the contents of the environment directory if it
already exists, before environment creation.
--upgrade Upgrade the environment directory to use this version
of Python, assuming Python has been upgraded in-place.
--without-pip Skips installing or upgrading pip in the virtual
environment (pip is bootstrapped by default)
--prompt PROMPT Provides an alternative prompt prefix for this
environment.
--upgrade-deps Upgrade core dependencies (pip) to the
latest version in PyPI
Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory.
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
[--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade-deps]
[--without-scm-ignore-file]
ENV_DIR [ENV_DIR ...]
Creates virtual Python environments in one or more target directories.
positional arguments:
ENV_DIR A directory to create the environment in.
options:
-h, --help show this help message and exit
--system-site-packages
Give the virtual environment access to the system
site-packages dir.
--symlinks Try to use symlinks rather than copies, when
symlinks are not the default for the platform.
--copies Try to use copies rather than symlinks, even when
symlinks are the default for the platform.
--clear Delete the contents of the environment directory if
it already exists, before environment creation.
--upgrade Upgrade the environment directory to use this
version of Python, assuming Python has been upgraded
in-place.
--without-pip Skips installing or upgrading pip in the virtual
environment (pip is bootstrapped by default)
--prompt PROMPT Provides an alternative prompt prefix for this
environment.
--upgrade-deps Upgrade core dependencies (pip) to the latest
version in PyPI
--without-scm-ignore-file
Skips adding the default SCM ignore file to the
environment directory (the default is a .gitignore
file).
Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory.
.. versionchanged:: 3.13
``--without-scm-ignore-file`` was added along with creating an ignore file
for ``git`` by default.
.. versionchanged:: 3.12
Expand Down
72 changes: 37 additions & 35 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _check_output_of_default_create(self):
os.path.realpath(sys.executable), data)
copies = '' if os.name=='nt' else ' --copies'
cmd = (f'command = {sys.executable} -m venv{copies} --without-pip '
f'--without-gitignore {self.env_dir}')
f'--without-scm-ignore-file {self.env_dir}')
self.assertIn(cmd, data)
fn = self.get_env_file(self.bindir, self.exe)
if not os.path.exists(fn): # diagnostics for Windows buildbot failures
Expand All @@ -148,37 +148,37 @@ def _check_output_of_default_create(self):
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)

def test_config_file_command_key(self):
attrs = [
(None, None),
('symlinks', '--copies'),
('with_pip', '--without-pip'),
('system_site_packages', '--system-site-packages'),
('clear', '--clear'),
('upgrade', '--upgrade'),
('upgrade_deps', '--upgrade-deps'),
('prompt', '--prompt'),
('gitignore', '--without-gitignore'),
options = [
(None, None, None), # Default case.
('--copies', 'symlinks', False),
('--without-pip', 'with_pip', False),
('--system-site-packages', 'system_site_packages', True),
('--clear', 'clear', True),
('--upgrade', 'upgrade', True),
('--upgrade-deps', 'upgrade_deps', True),
('--prompt', 'prompt', True),
('--without-scm-ignore-file', 'scm_ignore_file', None),
]
negated_attrs = {'with_pip', 'symlinks', 'gitignore'}
for attr, opt in attrs:
rmtree(self.env_dir)
if not attr:
b = venv.EnvBuilder()
else:
b = venv.EnvBuilder(
**{attr: attr not in negated_attrs})
b.upgrade_dependencies = Mock() # avoid pip command to upgrade deps
b._setup_pip = Mock() # avoid pip setup
self.run_with_capture(b.create, self.env_dir)
data = self.get_text_file_contents('pyvenv.cfg')
if not attr:
for opt in ('--system-site-packages', '--clear', '--upgrade',
'--upgrade-deps', '--prompt'):
self.assertNotRegex(data, rf'command = .* {opt}')
elif os.name=='nt' and attr=='symlinks':
pass
else:
self.assertRegex(data, rf'command = .* {opt}')
for opt, attr, value in options:
with self.subTest(opt=opt, attr=attr, value=value):
rmtree(self.env_dir)
if not attr:
kwargs = {}
else:
kwargs = {attr: value}
b = venv.EnvBuilder(**kwargs)
b.upgrade_dependencies = Mock() # avoid pip command to upgrade deps
b._setup_pip = Mock() # avoid pip setup
self.run_with_capture(b.create, self.env_dir)
data = self.get_text_file_contents('pyvenv.cfg')
if not attr or opt.endswith('git'):
for opt in ('--system-site-packages', '--clear', '--upgrade',
'--upgrade-deps', '--prompt'):
self.assertNotRegex(data, rf'command = .* {opt}')
elif os.name=='nt' and attr=='symlinks':
pass
else:
self.assertRegex(data, rf'command = .* {opt}')

def test_prompt(self):
env_name = os.path.split(self.env_dir)[1]
Expand Down Expand Up @@ -589,7 +589,7 @@ def test_zippath_from_non_installed_posix(self):
"-m",
"venv",
"--without-pip",
"--without-gitignore",
"--without-scm-ignore-file",
self.env_dir]
# Our fake non-installed python is not fully functional because
# it cannot find the extensions. Set PYTHONPATH so it can run the
Expand Down Expand Up @@ -621,6 +621,7 @@ def test_zippath_from_non_installed_posix(self):
out, err = check_output(cmd)
self.assertTrue(zip_landmark.encode() in out)

@requireVenvCreate
def test_activate_shell_script_has_no_dos_newlines(self):
"""
Test that the `activate` shell script contains no CR LF.
Expand All @@ -637,12 +638,13 @@ def test_activate_shell_script_has_no_dos_newlines(self):
error_message = f"CR LF found in line {i}"
self.assertFalse(line.endswith(b'\r\n'), error_message)

def test_gitignore(self):
@requireVenvCreate
def test_create_git_ignore_file(self):
"""
Test that a .gitignore file is created when requested.
Test that a .gitignore file is created.
The file should contain a `*\n` line.
"""
self.run_with_capture(venv.create, self.env_dir, gitignore=True)
self.run_with_capture(venv.create, self.env_dir, scm_ignore_file='git')
file_lines = self.get_text_file_contents('.gitignore').splitlines()
self.assertIn('*', file_lines)

Expand Down
35 changes: 18 additions & 17 deletions Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ class EnvBuilder:
environment
:param prompt: Alternative terminal prefix for the environment.
:param upgrade_deps: Update the base venv modules to the latest on PyPI
:param gitignore: Create a .gitignore file in the environment directory
which causes it to be ignored by git.
:param scm_ignore_file: Create an ignore file for the specified SCM.
"""

def __init__(self, system_site_packages=False, clear=False,
symlinks=False, upgrade=False, with_pip=False, prompt=None,
upgrade_deps=False, *, gitignore=False):
upgrade_deps=False, *, scm_ignore_file=None):
self.system_site_packages = system_site_packages
self.clear = clear
self.symlinks = symlinks
Expand All @@ -58,7 +57,9 @@ def __init__(self, system_site_packages=False, clear=False,
prompt = os.path.basename(os.getcwd())
self.prompt = prompt
self.upgrade_deps = upgrade_deps
self.gitignore = gitignore
if scm_ignore_file:
scm_ignore_file = scm_ignore_file.lower()
self.scm_ignore_file = scm_ignore_file

def create(self, env_dir):
"""
Expand All @@ -69,8 +70,8 @@ def create(self, env_dir):
"""
env_dir = os.path.abspath(env_dir)
context = self.ensure_directories(env_dir)
if self.gitignore:
self._setup_gitignore(context)
if self.scm_ignore_file:
getattr(self, f"create_{self.scm_ignore_file}_ignore_file")(context)
# See issue 24875. We need system_site_packages to be False
# until after pip is installed.
true_system_site_packages = self.system_site_packages
Expand Down Expand Up @@ -215,8 +216,8 @@ def create_configuration(self, context):
args.append('--upgrade-deps')
if self.orig_prompt is not None:
args.append(f'--prompt="{self.orig_prompt}"')
if not self.gitignore:
args.append('--without-gitignore')
if not self.scm_ignore_file:
args.append('--without-scm-ignore-file')

args.append(context.env_dir)
args = ' '.join(args)
Expand Down Expand Up @@ -285,7 +286,7 @@ def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):

shutil.copyfile(src, dst)

def _setup_gitignore(self, context):
def create_git_ignore_file(self, context):
"""
Create a .gitignore file in the environment directory.
Expand Down Expand Up @@ -482,12 +483,12 @@ 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,
*, gitignore=False):
*, scm_ignore_file=None):
"""Create a virtual environment in a directory."""
builder = EnvBuilder(system_site_packages=system_site_packages,
clear=clear, symlinks=symlinks, with_pip=with_pip,
prompt=prompt, upgrade_deps=upgrade_deps,
gitignore=gitignore)
scm_ignore_file=scm_ignore_file)
builder.create(env_dir)


Expand Down Expand Up @@ -547,11 +548,11 @@ def main(args=None):
dest='upgrade_deps',
help=f'Upgrade core dependencies ({", ".join(CORE_VENV_DEPS)}) '
'to the latest version in PyPI')
parser.add_argument('--without-gitignore', dest='gitignore',
default=True, action='store_false',
help='Skips adding a .gitignore file to the '
'environment directory which causes git to ignore '
'the environment directory.')
parser.add_argument('--without-scm-ignore-file', dest='scm_ignore_file',
action='store_const', const=None, default='git',
help='Skips adding the default SCM ignore file to the '
'environment directory (the default is a '
'.gitignore file).')
options = parser.parse_args(args)
if options.upgrade and options.clear:
raise ValueError('you cannot supply --upgrade and --clear together.')
Expand All @@ -562,7 +563,7 @@ def main(args=None):
with_pip=options.with_pip,
prompt=options.prompt,
upgrade_deps=options.upgrade_deps,
gitignore=options.gitignore)
scm_ignore_file=options.scm_ignore_file)
for d in options.dirs:
builder.create(d)

Expand Down

0 comments on commit ce17684

Please sign in to comment.