Skip to content
Merged
16 changes: 16 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Changes in jupyter-core
=======================

4.9
---

4.9.0
~~~~~

`on
GitHub <https://github.com/jupyter/jupyter_core/releases/tag/4.9.0>`__

See the `jupyter_core
4.9 <https://github.com/jupyter/jupyter_core/milestone/21?closed=1>`__
milestone on GitHub for the full list of pull requests and issues closed.

- Add Python site user base subdirectories to config and data user-level paths if ``site.ENABLE_USER_SITE`` is True. One way to disable these directory additions is to set the ``PYTHONNOUSERSITE`` environment variable. These locations can be customized by setting the ``PYTHONUSERBASE`` environment variable. (:ghpull:`242`)


4.8
---

Expand Down
6 changes: 6 additions & 0 deletions jupyter_core/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import json
import os
from shutil import which
import site
import sys
import sysconfig
from subprocess import Popen
Expand Down Expand Up @@ -257,6 +258,11 @@ def main():
else:
print("JUPYTER_CONFIG_DIR is not set, so we use the default user-level config directory")

if site.ENABLE_USER_SITE:
print(f"Python's site.ENABLE_USER_SITE is True, so we add the user site directory '{site.getuserbase()}'")
else:
print(f"Python's site.ENABLE_USER_SITE is not True, so we do not add the Python site user directory '{site.getuserbase()}'")

# data path list
if env.get('JUPYTER_PATH'):
print(f"JUPYTER_PATH is set to '{env.get('JUPYTER_PATH')}', which is prepended to the data paths")
Expand Down
37 changes: 28 additions & 9 deletions jupyter_core/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import sys
import stat
import errno
import site
import tempfile
import warnings
from pathlib import Path
Expand Down Expand Up @@ -141,6 +142,10 @@ def jupyter_path(*subdirs):
If the JUPYTER_PREFER_ENV_PATH environment variable is set, the environment-level
directories will have priority over user-level directories.

If the Python site.ENABLE_USER_SITE variable is True, we also add the
appropriate Python user site subdirectory to the user-level directories.


If ``*subdirs`` are given, that subdirectory will be added to each element.

Examples:
Expand All @@ -161,14 +166,19 @@ def jupyter_path(*subdirs):
)

# Next is environment or user, depending on the JUPYTER_PREFER_ENV_PATH flag
user = jupyter_data_dir()
user = [jupyter_data_dir()]
if site.ENABLE_USER_SITE:
userdir = os.path.join(site.getuserbase(), 'share', 'jupyter')
if userdir not in user:
user.append(userdir)

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

if envset('JUPYTER_PREFER_ENV_PATH'):
paths.extend(env)
paths.append(user)
paths.extend(user)
else:
paths.append(user)
paths.extend(user)
paths.extend(env)

# finally, system
Expand Down Expand Up @@ -197,9 +207,13 @@ def jupyter_path(*subdirs):

def jupyter_config_path():
"""Return the search path for Jupyter config files as a list.

If the JUPYTER_PREFER_ENV_PATH environment variable is set, the environment-level
directories will have priority over user-level directories.

If the JUPYTER_PREFER_ENV_PATH environment variable is set, the
environment-level directories will have priority over user-level
directories.

If the Python site.ENABLE_USER_SITE variable is True, we also add the
appropriate Python user site subdirectory to the user-level directories.
"""
if os.environ.get('JUPYTER_NO_CONFIG'):
# jupyter_config_dir makes a blank config when JUPYTER_NO_CONFIG is set.
Expand All @@ -215,14 +229,19 @@ def jupyter_config_path():
)

# Next is environment or user, depending on the JUPYTER_PREFER_ENV_PATH flag
user = jupyter_config_dir()
user = [jupyter_config_dir()]
if site.ENABLE_USER_SITE:
userdir = os.path.join(site.getuserbase(), 'etc', 'jupyter')
if userdir not in user:
user.append(userdir)

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

if envset('JUPYTER_PREFER_ENV_PATH'):
paths.extend(env)
paths.append(user)
paths.extend(user)
else:
paths.append(user)
paths.extend(user)
paths.extend(env)

# Finally, system path
Expand Down
9 changes: 9 additions & 0 deletions jupyter_core/tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
)


resetenv = patch.dict(os.environ)

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

def teardown_module():
resetenv.stop()

def get_jupyter_output(cmd):
"""Get output of a jupyter command"""
if not isinstance(cmd, list):
Expand Down
64 changes: 58 additions & 6 deletions jupyter_core/tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import tempfile
from unittest.mock import patch
import pytest
import site
import subprocess
import sys
import warnings
Expand Down Expand Up @@ -47,14 +48,24 @@

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'})

resetenv = patch.dict(os.environ)

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

def teardown_module():
resetenv.stop()



def realpath(path):
return os.path.abspath(os.path.realpath(os.path.expanduser(path)))

home_jupyter = realpath('~/.jupyter')


def test_envset():
true_values = ['', 'True', 'on', 'yes', 'Y', '1', 'anything']
false_values = ['n', 'No', 'N', 'fAlSE', '0', '0.0', 'Off']
Expand Down Expand Up @@ -184,8 +195,27 @@ def test_jupyter_path():
assert path[0] == jupyter_data_dir()
assert path[-2:] == system_path

def test_jupyter_path_user_site():
with no_config_env, patch.object(site, 'ENABLE_USER_SITE', True):
path = jupyter_path()

# deduplicated expected values
values = list(dict.fromkeys([
jupyter_data_dir(),
os.path.join(site.getuserbase(), 'share', 'jupyter'),
paths.ENV_JUPYTER_PATH[0]
]))
for p,v in zip(path, values):
assert p == v

def test_jupyter_path_no_user_site():
with no_config_env, patch.object(site, 'ENABLE_USER_SITE', False):
path = jupyter_path()
assert path[0] == jupyter_data_dir()
assert path[1] == paths.ENV_JUPYTER_PATH[0]

def test_jupyter_path_prefer_env():
with patch.dict('os.environ', {'JUPYTER_PREFER_ENV_PATH': 'true'}):
with prefer_env:
path = jupyter_path()
assert path[0] == paths.ENV_JUPYTER_PATH[0]
assert path[1] == jupyter_data_dir()
Expand Down Expand Up @@ -213,15 +243,37 @@ def test_jupyter_path_subdir():
assert p.endswith(pjoin('', 'sub1', 'sub2'))

def test_jupyter_config_path():
path = jupyter_config_path()
with patch.object(site, 'ENABLE_USER_SITE', True):
path = jupyter_config_path()

# deduplicated expected values
values = list(dict.fromkeys([
jupyter_config_dir(),
os.path.join(site.getuserbase(), 'etc', 'jupyter'),
paths.ENV_CONFIG_PATH[0]
]))
for p,v in zip(path, values):
assert p == v

def test_jupyter_config_path_no_user_site():
with patch.object(site, 'ENABLE_USER_SITE', False):
path = jupyter_config_path()
assert path[0] == jupyter_config_dir()
assert path[1] == paths.ENV_CONFIG_PATH[0]


def test_jupyter_config_path_prefer_env():
with patch.dict('os.environ', {'JUPYTER_PREFER_ENV_PATH': 'true'}):
with prefer_env, patch.object(site, 'ENABLE_USER_SITE', True):
path = jupyter_config_path()
assert path[0] == paths.ENV_CONFIG_PATH[0]
assert path[1] == jupyter_config_dir()

# deduplicated expected values
values = list(dict.fromkeys([
paths.ENV_CONFIG_PATH[0],
jupyter_config_dir(),
os.path.join(site.getuserbase(), 'etc', 'jupyter')
]))
for p,v in zip(path, values):
assert p == v

def test_jupyter_config_path_env():
path_env = os.pathsep.join([
Expand Down