Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to detect if we are in a virtual environment and change path precedence accordingly #286

Merged
merged 8 commits into from Aug 24, 2022
6 changes: 3 additions & 3 deletions jupyter_core/command.py
Expand Up @@ -231,13 +231,13 @@ def main():
if args.debug:
env = os.environ

if paths.envset("JUPYTER_PREFER_ENV_PATH"):
if paths.prefer_environment_over_user():
print(
"JUPYTER_PREFER_ENV_PATH is set, making the environment-level path preferred over the user-level path for data and config"
"JUPYTER_PREFER_ENV_PATH is set to a true value, or JUPYTER_PREFER_ENV_PATH is not set and we detected a virtual environment, making the environment-level path preferred over the user-level path for data and config"
)
else:
print(
"JUPYTER_PREFER_ENV_PATH is not set, making the user-level path preferred over the environment-level path for data and config"
"JUPYTER_PREFER_ENV_PATH is set to a false value, or JUPYTER_PREFER_ENV_PATH is not set and we did not detect a virtual environment, making the user-level path preferred over the environment-level path for data and config"
)

# config path list
Expand Down
32 changes: 27 additions & 5 deletions jupyter_core/paths.py
Expand Up @@ -26,13 +26,18 @@
UF_HIDDEN = getattr(stat, "UF_HIDDEN", 32768)


def envset(name):
"""Return True if the given environment variable is set
def envset(name, default=False):
"""Return the boolean value of a given environment variable.

An environment variable is considered set if it is assigned to a value
other than 'no', 'n', 'false', 'off', '0', or '0.0' (case insensitive)

If the environment variable is not defined, the default value is returned.
"""
return os.environ.get(name, "no").lower() not in ["no", "n", "false", "off", "0", "0.0"]
if name not in os.environ:
return default

return os.environ[name].lower() not in ["no", "n", "false", "off", "0", "0.0"]


def get_home_dir():
Expand All @@ -47,6 +52,23 @@ def get_home_dir():
_dtemps: dict = {}


def prefer_environment_over_user():
"""Determine if environment-level paths should take precedence over user-level paths."""
# If JUPYTER_PREFER_ENV_PATH is defined, that signals user intent, so return its value
if "JUPYTER_PREFER_ENV_PATH" in os.environ:
return envset("JUPYTER_PREFER_ENV_PATH")

# If we are in a Python virtualenv, default to True (see https://docs.python.org/3/library/venv.html#venv-def)
if sys.prefix != sys.base_prefix:
return True

# If sys.prefix indicates Python comes from a conda/mamba environment, default to True
if "CONDA_PREFIX" in os.environ and sys.prefix.startswith(os.environ["CONDA_PREFIX"]):
return True

return False


def _mkdtemp_once(name):
"""Make or reuse a temporary directory.

Expand Down Expand Up @@ -184,7 +206,7 @@ def jupyter_path(*subdirs):

env = [p for p in ENV_JUPYTER_PATH if p not in SYSTEM_JUPYTER_PATH]

if envset("JUPYTER_PREFER_ENV_PATH"):
if prefer_environment_over_user():
paths.extend(env)
paths.extend(user)
else:
Expand Down Expand Up @@ -253,7 +275,7 @@ def jupyter_config_path():

env = [p for p in ENV_CONFIG_PATH if p not in SYSTEM_CONFIG_PATH]

if envset("JUPYTER_PREFER_ENV_PATH"):
if prefer_environment_over_user():
paths.extend(env)
paths.extend(user)
else:
Expand Down
1 change: 0 additions & 1 deletion jupyter_core/tests/test_command.py
Expand Up @@ -23,7 +23,6 @@

def setup_module():
resetenv.start()
os.environ.pop("JUPYTER_PREFER_ENV_PATH", None)


def teardown_module():
Expand Down
26 changes: 24 additions & 2 deletions jupyter_core/tests/test_paths.py
Expand Up @@ -25,6 +25,7 @@
jupyter_data_dir,
jupyter_path,
jupyter_runtime_dir,
prefer_environment_over_user,
secure_write,
)

Expand Down Expand Up @@ -61,13 +62,16 @@
jupyter_config_env = "/jupyter-cfg"
config_env = patch.dict("os.environ", {"JUPYTER_CONFIG_DIR": jupyter_config_env})
prefer_env = patch.dict("os.environ", {"JUPYTER_PREFER_ENV_PATH": "True"})
prefer_user = patch.dict("os.environ", {"JUPYTER_PREFER_ENV_PATH": "False"})

resetenv = patch.dict(os.environ)


def setup_module():
resetenv.start()
os.environ.pop("JUPYTER_PREFER_ENV_PATH", None)
# For these tests, default to preferring the user-level over environment-level paths
# Tests can override this preference using the prefer_env decorator/context manager
os.environ["JUPYTER_PREFER_ENV_PATH"] = "no"


def teardown_module():
Expand All @@ -89,7 +93,10 @@ def test_envset():
assert paths.envset(f"FOO_{v}")
for v in false_values:
assert not paths.envset(f"FOO_{v}")
assert not paths.envset("THIS_VARIABLE_SHOULD_NOT_BE_SET")
# Test default value is False
assert paths.envset("THIS_VARIABLE_SHOULD_NOT_BE_SET") is False
# Test envset returns the given default if supplied
assert paths.envset("THIS_VARIABLE_SHOULD_NOT_BE_SET", None) is None


@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
Expand Down Expand Up @@ -328,6 +335,21 @@ def test_jupyter_config_path_env():
assert path[:2] == [pjoin("foo", "bar"), pjoin("bar", "baz")]


def test_prefer_environment_over_user():
with prefer_env:
assert prefer_environment_over_user() is True

with prefer_user:
assert prefer_environment_over_user() is False

# Test default if environment variable is not set, and try to determine if we are in a virtual environment
os.environ.pop("JUPYTER_PREFER_ENV_PATH", None)
in_venv = sys.prefix != sys.base_prefix or (
"CONDA_PREFIX" in os.environ and sys.prefix.startswith(os.environ["CONDA_PREFIX"])
)
assert prefer_environment_over_user() is in_venv


def test_is_hidden():
with tempfile.TemporaryDirectory() as root:
subdir1 = os.path.join(root, "subdir")
Expand Down