Skip to content

Commit ea91ea3

Browse files
mballanceCopilot
andcommitted
Make --system-site-packages venv opt-in; add --py-system-site-packages flag
Virtual environments are now isolated by default. The previous behavior of always passing --system-site-packages to venv/uv is removed. Changes: - setup_venv() in utils.py gains system_site_packages=False parameter - project_ops.py threads args.py_system_site_packages through to setup_venv() - __main__.py adds --py-system-site-packages to both update and clone commands - test/unit/test_venv.py: new tests for isolated (default) and opt-in venv creation - docs: update reference.rst, python_packages.rst, getting_started.rst Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e7a9101 commit ea91ea3

File tree

7 files changed

+138
-7
lines changed

7 files changed

+138
-7
lines changed

docs/source/getting_started.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ Clone Command Options
6969
Choose whether ``ivpm update`` should use "uv" or "pip"
7070
to manage the project-local Python virtual environment.
7171

72+
``--py-system-site-packages``
73+
Opt in to inheriting system site-packages inside the virtual environment.
74+
By default the environment is **isolated**; use this flag only when you
75+
need access to system-installed packages (e.g. hardware-specific bindings).
76+
7277
**Examples:**
7378

7479
.. code-block:: bash

docs/source/python_packages.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ Creates::
3636
└── ...
3737

3838
The virtual environment is created using Python's built-in ``venv`` module.
39+
By default the environment is **isolated**: packages installed in the base
40+
Python or system Python are **not** visible inside it. This ensures
41+
reproducible, self-contained workspaces.
42+
43+
To opt in to inheriting system site-packages (e.g. for hardware-specific
44+
Python extensions that cannot be installed via pip), pass
45+
``--py-system-site-packages`` to ``ivpm update`` or ``ivpm clone``:
46+
47+
.. code-block:: bash
48+
49+
$ ivpm update --py-system-site-packages
50+
$ ivpm clone https://github.com/org/project --py-system-site-packages
3951
4052
Package Manager Selection
4153
--------------------------

docs/source/reference.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ Create a new workspace from a Git repository.
239239
``--py-pip``
240240
Use 'pip' for Python package management
241241

242+
``--py-system-site-packages``
243+
Inherit the base Python's system site-packages inside the created virtual
244+
environment. By default the environment is **isolated** (system packages
245+
are not visible). Pass this flag only when you intentionally need access
246+
to system-installed packages (e.g. hardware-specific Python bindings that
247+
cannot be installed via pip).
248+
242249
**Examples:**
243250

244251
.. code-block:: bash
@@ -523,6 +530,12 @@ Fetch dependencies and initialize environment.
523530
``--py-pip``
524531
Use 'pip' for package management
525532

533+
``--py-system-site-packages``
534+
Inherit the base Python's system site-packages inside the created virtual
535+
environment. By default the environment is **isolated** (system packages
536+
are not visible). Pass this flag only when you intentionally need access
537+
to system-installed packages.
538+
526539
``--lock-file <path>``
527540
Reproduce workspace from a ``package-lock.json`` file. ``ivpm.yaml``
528541
is not read for packages; the lock file supplies the complete package

src/ivpm/__main__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ def get_parser(parser_ext : List = None, options_ext : List = None):
142142
help="Use 'uv' to manage virtual environment")
143143
clone_cmd.add_argument("--py-pip", dest="py_pip", action="store_true",
144144
help="Use 'pip' to manage virtual environment")
145+
clone_cmd.add_argument("--py-system-site-packages", dest="py_system_site_packages",
146+
action="store_true", default=False,
147+
help="Inherit system site-packages in the virtual environment (default: isolated)")
145148
clone_cmd.set_defaults(func=CmdClone())
146149
subcommands["clone"] = clone_cmd
147150

@@ -170,8 +173,11 @@ def get_parser(parser_ext : List = None, options_ext : List = None):
170173
help="Use 'uv' to manage virtual environment",
171174
action="store_true")
172175
update_cmd.add_argument("--py-pip",
173-
help="Use 'uv' to manage virtual environment",
176+
help="Use 'pip' to manage virtual environment",
174177
action="store_true")
178+
update_cmd.add_argument("--py-system-site-packages", dest="py_system_site_packages",
179+
action="store_true", default=False,
180+
help="Inherit system site-packages in the virtual environment (default: isolated)")
175181
update_cmd.add_argument("--lock-file", dest="lock_file", default=None,
176182
help="Reproduce workspace from a package-lock.json file (ignores ivpm.yaml)")
177183
update_cmd.add_argument("--refresh-all", dest="refresh_all",

src/ivpm/project_ops.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ def update(self,
7878
uv_pip = "uv"
7979
elif hasattr(args, "py_pip") and args.py_pip:
8080
uv_pip = "pip"
81+
system_site_packages = (
82+
hasattr(args, "py_system_site_packages") and
83+
bool(args.py_system_site_packages)
84+
)
8185

8286
# Signal venv creation start
8387
venv_start_time = time.time()
@@ -89,7 +93,8 @@ def update(self,
8993
ivpm_python = setup_venv(
9094
os.path.join(deps_dir, "python"),
9195
uv_pip=uv_pip,
92-
suppress_output=suppress_output
96+
suppress_output=suppress_output,
97+
system_site_packages=system_site_packages
9398
)
9499
# Signal venv creation complete
95100
event_dispatcher.dispatch(UpdateEvent(

src/ivpm/utils.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def get_venv_python(python_dir):
7676

7777
return ivpm_python
7878

79-
def setup_venv(python_dir, uv_pip="auto", suppress_output=False):
79+
def setup_venv(python_dir, uv_pip="auto", suppress_output=False, system_site_packages=False):
8080
note("creating Python virtual environment")
8181

8282
if uv_pip == "auto":
@@ -106,9 +106,10 @@ def setup_venv(python_dir, uv_pip="auto", suppress_output=False):
106106
"venv",
107107
"--python",
108108
python,
109-
"--system-site-packages",
110-
python_dir
111109
]
110+
if system_site_packages:
111+
cmd.append("--system-site-packages")
112+
cmd.append(python_dir)
112113

113114
result = subprocess.run(
114115
cmd,
@@ -145,14 +146,18 @@ def setup_venv(python_dir, uv_pip="auto", suppress_output=False):
145146
ivpm_python = get_venv_python(python_dir)
146147
else:
147148
note("Using 'pip' to manage virtual environment")
149+
venv_cmd = [python, "-m", "venv"]
150+
if system_site_packages:
151+
venv_cmd.append("--system-site-packages")
152+
venv_cmd.append(python_dir)
148153
if suppress_output:
149154
result = subprocess.run(
150-
[python, "-m", "venv", "--system-site-packages", python_dir],
155+
venv_cmd,
151156
stdout=stdout_arg,
152157
stderr=stderr_arg
153158
)
154159
else:
155-
os.system(python + " -m venv --system-site-packages " + python_dir)
160+
os.system(" ".join(venv_cmd))
156161
note("upgrading pip")
157162
ivpm_python = get_venv_python(python_dir)
158163

test/unit/test_venv.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Tests for virtual environment creation behavior.
3+
4+
Verifies that:
5+
- Default venv creation produces an isolated environment (no system-site-packages).
6+
- Opt-in via py_system_site_packages=True produces a venv that includes system site-packages.
7+
"""
8+
import os
9+
import configparser
10+
from .test_base import TestBase
11+
12+
13+
_SIMPLE_IVPM_YAML = """
14+
package:
15+
name: venv_test
16+
dep-sets:
17+
- name: default-dev
18+
deps:
19+
- name: leaf_proj1
20+
url: file://${DATA_DIR}/leaf_proj1
21+
src: dir
22+
"""
23+
24+
25+
def _read_pyvenv_cfg(python_dir):
26+
"""Parse pyvenv.cfg from a virtualenv directory."""
27+
cfg_path = os.path.join(python_dir, "pyvenv.cfg")
28+
cfg = configparser.ConfigParser()
29+
# pyvenv.cfg has no section header; add a fake one
30+
with open(cfg_path) as fp:
31+
content = "[root]\n" + fp.read()
32+
cfg.read_string(content)
33+
return dict(cfg["root"])
34+
35+
36+
class TestVenv(TestBase):
37+
38+
def _make_project(self):
39+
self.mkFile("ivpm.yaml", _SIMPLE_IVPM_YAML)
40+
41+
def test_venv_isolated_by_default(self):
42+
"""Default update must create an isolated venv (no system site-packages)."""
43+
self._make_project()
44+
45+
class Args:
46+
py_uv = False
47+
py_pip = True # force pip so we don't need uv
48+
py_system_site_packages = False
49+
anonymous_git = None
50+
log_level = "NONE"
51+
52+
self.ivpm_update(skip_venv=False, args=Args())
53+
54+
python_dir = os.path.join(self.testdir, "packages", "python")
55+
self.assertTrue(os.path.isdir(python_dir),
56+
"packages/python directory should exist after update")
57+
58+
cfg = _read_pyvenv_cfg(python_dir)
59+
value = cfg.get("include-system-site-packages", "false").strip().lower()
60+
self.assertEqual(value, "false",
61+
"Default venv must NOT include system site-packages; "
62+
"got include-system-site-packages = %r" % value)
63+
64+
def test_venv_system_site_packages_opt_in(self):
65+
"""Update with py_system_site_packages=True must create a venv that includes system site-packages."""
66+
self._make_project()
67+
68+
class Args:
69+
py_uv = False
70+
py_pip = True # force pip so we don't need uv
71+
py_system_site_packages = True
72+
anonymous_git = None
73+
log_level = "NONE"
74+
75+
self.ivpm_update(skip_venv=False, args=Args())
76+
77+
python_dir = os.path.join(self.testdir, "packages", "python")
78+
self.assertTrue(os.path.isdir(python_dir),
79+
"packages/python directory should exist after update")
80+
81+
cfg = _read_pyvenv_cfg(python_dir)
82+
value = cfg.get("include-system-site-packages", "false").strip().lower()
83+
self.assertEqual(value, "true",
84+
"Opt-in venv MUST include system site-packages; "
85+
"got include-system-site-packages = %r" % value)

0 commit comments

Comments
 (0)