Skip to content

Commit

Permalink
Use custom package metadata for augmenting Jupyter paths.
Browse files Browse the repository at this point in the history
This does not require packages to be imported in order to get the Jupyter paths, which are potentially costly steps. Instead, we rely strictly on scanning and getting package metadata.
  • Loading branch information
jasongrout committed Mar 5, 2021
1 parent b5dd3e0 commit 66351b0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 10 deletions.
8 changes: 8 additions & 0 deletions examples/jupyter_path_entrypoint_setuptools/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ include_package_data = True
zip_safe = False
python_requires = >=3.6

setup_requires =
jupyter_core
install_requires =
jupyter_core

# Jupyter directories are relative to the package root
jupyter_config_paths =
etc/jupyter
etc/another/jupyter
jupyter_data_paths = share/jupyter

[options.packages.find]
where =
src
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

__version__ = "0.1.0"

HERE = os.path.abspath(os.path.dirname(__file__))
# HERE = os.path.abspath(os.path.dirname(__file__))

JUPYTER_CONFIG_PATHS = [os.path.join(HERE, "etc", "jupyter")]
JUPYTER_DATA_PATHS = [os.path.join(HERE, "share", "jupyter")]
# JUPYTER_CONFIG_PATHS = [os.path.join(HERE, "etc", "jupyter")]
# JUPYTER_DATA_PATHS = [os.path.join(HERE, "share", "jupyter")]
31 changes: 25 additions & 6 deletions jupyter_core/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from contextlib import contextmanager

import entrypoints
import pkg_resources


pjoin = os.path.join
Expand Down Expand Up @@ -49,6 +50,18 @@ def _entry_point_paths(ep_group):
))
return paths

def _package_metadata(group):
"""Load extra jupyter paths from custom package metadata
"""
paths = []
filename = f'{group}.txt'
for distribution in sorted(pkg_resources.working_set, key=lambda x: x.key):
if distribution.has_metadata(filename) and distribution.has_metadata('top_level.txt'):
top_level = list(distribution.get_metadata_lines('top_level.txt'))[0]
localpaths = [f'{top_level}/{p}' for p in distribution.get_metadata_lines(filename)]
paths.extend(distribution.get_resource_filename(distribution, p) for p in localpaths if distribution.resource_isdir(p))
return paths

def envset(name):
"""Return True if the given environment variable is set
Expand Down Expand Up @@ -187,16 +200,19 @@ def jupyter_path(*subdirs):
# Next is environment or user, depending on the JUPYTER_PREFER_ENV_PATH flag
user = jupyter_data_dir()
env = [p for p in ENV_JUPYTER_PATH if p not in SYSTEM_JUPYTER_PATH]
entry_points = [p for p in _entry_point_paths(JUPYTER_DATA_PATH_ENTRY_POINT) if p not in SYSTEM_JUPYTER_PATH]
# entry_points = [p for p in _entry_point_paths(JUPYTER_DATA_PATH_ENTRY_POINT) if p not in SYSTEM_JUPYTER_PATH]
package_metadata = [p for p in _package_metadata(JUPYTER_DATA_PATH_ENTRY_POINT) if p not in SYSTEM_JUPYTER_PATH]

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

# finally, system
paths.extend(SYSTEM_JUPYTER_PATH)
Expand Down Expand Up @@ -244,16 +260,19 @@ def jupyter_config_path():
# Next is environment or user, depending on the JUPYTER_PREFER_ENV_PATH flag
user = jupyter_config_dir()
env = [p for p in ENV_CONFIG_PATH if p not in SYSTEM_CONFIG_PATH]
entry_points = [p for p in _entry_point_paths(JUPYTER_CONFIG_PATH_ENTRY_POINT) if p not in SYSTEM_CONFIG_PATH]
# entry_points = [p for p in _entry_point_paths(JUPYTER_CONFIG_PATH_ENTRY_POINT) if p not in SYSTEM_CONFIG_PATH]
package_metadata = [p for p in _package_metadata(JUPYTER_CONFIG_PATH_ENTRY_POINT) if p not in SYSTEM_CONFIG_PATH]

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

# Finally, system path
paths.extend(SYSTEM_CONFIG_PATH)
Expand Down
27 changes: 26 additions & 1 deletion jupyter_core/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,29 @@ def ensure_dir_exists(path, mode=0o777):
if e.errno != errno.EEXIST:
raise
if not os.path.isdir(path):
raise IOError("%r exists but is not a directory" % path)
raise IOError("%r exists but is not a directory" % path)

# from setuptools.config.ConfigHandler
def _parse_list(value, separator=','):
"""Represents value as a list.
Value is split either by separator (defaults to comma) or by lines.
:param value:
:param separator: List items separator character.
:rtype: list
"""
if isinstance(value, list): # _get_parser_compound case
return value

if '\n' in value:
value = value.splitlines()
else:
value = value.split(separator)

return [chunk.strip() for chunk in value if chunk.strip()]

def write_arg_list(cmd, basename, filename):
argname = os.path.splitext(basename)[0]
value = getattr(cmd.distribution, argname, None)
if value is not None:
value = "\n".join(_parse_list(value)) + "\n"
cmd.write_or_delete_file(argname, filename, value)
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ console_scripts =
jupyter = jupyter_core.command:main
jupyter-migrate = jupyter_core.migrate:main
jupyter-troubleshoot = jupyter_core.troubleshoot:main
distutils.setup_keywords =
jupyter_config_paths = setuptools.dist:assert_string_list
jupyter_data_paths = setuptools.dist:assert_string_list
egg_info.writers =
jupyter_config_paths.txt = jupyter_core.utils:write_arg_list
jupyter_data_paths.txt = jupyter_core.utils:write_arg_list

0 comments on commit 66351b0

Please sign in to comment.