From 1b133b590ef112d9bc47d22d986b6048823cc899 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sat, 21 Nov 2020 15:51:29 -0500 Subject: [PATCH 01/50] add data/config path entry_points, minimal example --- examples/jupyter_path_entrypoint/COPYING.md | 61 +++++++++++++++++++ examples/jupyter_path_entrypoint/MANIFEST.in | 1 + examples/jupyter_path_entrypoint/README.md | 4 ++ examples/jupyter_path_entrypoint/setup.cfg | 40 ++++++++++++ examples/jupyter_path_entrypoint/setup.py | 2 + .../src/entry_point_example/__init__.py | 8 +++ .../jupyter_config.d/entrypoint-example.json | 5 ++ jupyter_core/paths.py | 31 +++++++++- jupyter_core/tests/test_paths.py | 2 +- setup.cfg | 2 +- 10 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 examples/jupyter_path_entrypoint/COPYING.md create mode 100644 examples/jupyter_path_entrypoint/MANIFEST.in create mode 100644 examples/jupyter_path_entrypoint/README.md create mode 100644 examples/jupyter_path_entrypoint/setup.cfg create mode 100644 examples/jupyter_path_entrypoint/setup.py create mode 100644 examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py create mode 100644 examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json diff --git a/examples/jupyter_path_entrypoint/COPYING.md b/examples/jupyter_path_entrypoint/COPYING.md new file mode 100644 index 0000000..e5ae344 --- /dev/null +++ b/examples/jupyter_path_entrypoint/COPYING.md @@ -0,0 +1,61 @@ +# The Jupyter licensing terms + +Jupyter is licensed under the terms of the Modified BSD License (also known as +New or Revised or 3-Clause BSD), as follows: + +- Copyright (c) 2015-, Jupyter Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the Jupyter Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## About the Jupyter Development Team + +The Jupyter Development Team is the set of all contributors to the Jupyter +project. This includes all of the Jupyter subprojects. A full list with +details is kept in the documentation directory, in the file +`about/credits.txt`. + +The core team that coordinates development on GitHub can be found here: +https://github.com/ipython/. + +## Our Copyright Policy + +Jupyter uses a shared copyright model. Each contributor maintains copyright +over their contributions to Jupyter. It is important to note that these +contributions are typically only changes to the repositories. Thus, the Jupyter +source code in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire Jupyter +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the Jupyter repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + + # Copyright (c) Jupyter Development Team. + # Distributed under the terms of the Modified BSD License. diff --git a/examples/jupyter_path_entrypoint/MANIFEST.in b/examples/jupyter_path_entrypoint/MANIFEST.in new file mode 100644 index 0000000..9e77b3e --- /dev/null +++ b/examples/jupyter_path_entrypoint/MANIFEST.in @@ -0,0 +1 @@ +recursive-include src/entry_point_example *.* diff --git a/examples/jupyter_path_entrypoint/README.md b/examples/jupyter_path_entrypoint/README.md new file mode 100644 index 0000000..70f383b --- /dev/null +++ b/examples/jupyter_path_entrypoint/README.md @@ -0,0 +1,4 @@ +# entry_point_example + +A minimal example of a package which provides an additional `jupyter_path` value +via `entry_point`. diff --git a/examples/jupyter_path_entrypoint/setup.cfg b/examples/jupyter_path_entrypoint/setup.cfg new file mode 100644 index 0000000..0bab959 --- /dev/null +++ b/examples/jupyter_path_entrypoint/setup.cfg @@ -0,0 +1,40 @@ +[metadata] +name = entry_point_example +version = attr: entry_point_example.__version__ +description = a dummy module for testing jupyter_paths entrypoint +long_description = file: README.md +long_description_content_type = text/markdown +url = https://jupyter.org +author = Jupyter Development Team +author_email = jupyter@googlegroups.org +license = BSD +license_file = COPYING.md +classifiers = + Framework :: Jupyter + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: BSD License + Programming Language :: Python + +[options] +package_dir = + = src + +packages = find: +include_package_data = True +zip_safe = False +python_requires = >=3.6 + +install_requires = + jupyter_core + +[options.packages.find] +where = + src + +[options.entry_points] +jupyter_config_paths = + entry-point-example = entry_point_example:JUPYTER_CONFIG +# similarly, adding a data path would be like +# jupyter_data_paths = +# entry-point-example = entry_point_example:JUPYTER_DATA diff --git a/examples/jupyter_path_entrypoint/setup.py b/examples/jupyter_path_entrypoint/setup.py new file mode 100644 index 0000000..a4f49f9 --- /dev/null +++ b/examples/jupyter_path_entrypoint/setup.py @@ -0,0 +1,2 @@ +import setuptools +setuptools.setup() diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py b/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py new file mode 100644 index 0000000..4243e3e --- /dev/null +++ b/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py @@ -0,0 +1,8 @@ +"""an example of using the jupyter_paths entry_point""" +import os + +__version__ = "0.1.0" + +HERE = os.path.abspath(os.path.dirname(__file__)) + +JUPYTER_CONFIG = [os.path.join(HERE, "etc", "jupyter")] diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json b/examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json new file mode 100644 index 0000000..cb32537 --- /dev/null +++ b/examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json @@ -0,0 +1,5 @@ +{ + "LoggingConfigurable": { + "log_level": "DEBUG" + } +} diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 3005f96..536e38f 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -14,6 +14,9 @@ import errno import tempfile import warnings +import traceback +import entrypoints +from functools import lru_cache from contextlib import contextmanager @@ -23,6 +26,26 @@ # It is used by BSD to indicate hidden files. UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) +# the group names for entry_points in pyproject.toml, setup.py and .cfg to +# provide discoverable paths +# the entrypoint target must be a list of strings to absolute paths +JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_paths" +JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_paths" + +@lru_cache(maxsize=10) +def _entry_point_paths(ep_group): + paths = [] + """Load extra jupyter paths from entry_points, sorted by their entry_point name + """ + for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): + try: + paths.extend(ep.load()) + except: + warnings.warn('Failed to load jupyter_paths from entry_point "{}"\n{}'.format( + name, + traceback.format_exc() + )) + return paths def envset(name): """Return True if the given environment variable is set @@ -170,6 +193,9 @@ def jupyter_path(*subdirs): paths.append(user) paths.extend(env) + # entry_points + paths.extend(_entry_point_paths(JUPYTER_DATA_PATH_ENTRY_POINT)) + # finally, system paths.extend(SYSTEM_JUPYTER_PATH) @@ -196,7 +222,7 @@ 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. """ @@ -224,6 +250,9 @@ def jupyter_config_path(): paths.append(user) paths.extend(env) + # entry_points + paths.extend(_entry_point_paths(JUPYTER_CONFIG_PATH_ENTRY_POINT)) + # Finally, system path paths.extend(SYSTEM_CONFIG_PATH) return paths diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index fe3544f..b54586c 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -16,7 +16,7 @@ from jupyter_core.paths import ( jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir, jupyter_path, jupyter_config_path, ENV_JUPYTER_PATH, - secure_write, is_hidden, is_file_hidden + secure_write, is_hidden, is_file_hidden, JUPYTER_PATH_ENTRY_POINT ) from .mocking import darwin, windows, linux diff --git a/setup.cfg b/setup.cfg index f68a494..5f843c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ include_package_data = True python_requires = >=3.6 install_requires = traitlets + entrypoints pywin32>=1.0 ; sys_platform == 'win32' [options.entry_points] @@ -31,4 +32,3 @@ console_scripts = jupyter = jupyter_core.command:main jupyter-migrate = jupyter_core.migrate:main jupyter-troubleshoot = jupyter_core.troubleshoot:main - From 929726b6f61b9fe373b3939e780010d4103f9342 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sat, 21 Nov 2020 16:04:52 -0500 Subject: [PATCH 02/50] remove JUPYTER_PATH_ENTRY_POINT from test, since not testing yet --- jupyter_core/tests/test_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index b54586c..fe3544f 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -16,7 +16,7 @@ from jupyter_core.paths import ( jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir, jupyter_path, jupyter_config_path, ENV_JUPYTER_PATH, - secure_write, is_hidden, is_file_hidden, JUPYTER_PATH_ENTRY_POINT + secure_write, is_hidden, is_file_hidden ) from .mocking import darwin, windows, linux From 8a110ef1abf37e029d1f42140419d431fc43929e Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 3 Mar 2021 22:15:41 -0800 Subject: [PATCH 03/50] Make entry point paths come just after the environment-level paths. Since entry points come from packages installed in the environment, I think it makes sense that they are treated like the environment paths --- jupyter_core/paths.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 536e38f..ed86ee4 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -34,9 +34,9 @@ @lru_cache(maxsize=10) def _entry_point_paths(ep_group): - paths = [] """Load extra jupyter paths from entry_points, sorted by their entry_point name """ + paths = [] for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): try: paths.extend(ep.load()) @@ -185,16 +185,16 @@ 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] if envset('JUPYTER_PREFER_ENV_PATH'): paths.extend(env) + paths.extend(entry_points) paths.append(user) else: paths.append(user) paths.extend(env) - - # entry_points - paths.extend(_entry_point_paths(JUPYTER_DATA_PATH_ENTRY_POINT)) + paths.extend(entry_points) # finally, system paths.extend(SYSTEM_JUPYTER_PATH) @@ -242,16 +242,16 @@ 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] if envset('JUPYTER_PREFER_ENV_PATH'): paths.extend(env) + paths.extend(entry_points) paths.append(user) else: paths.append(user) paths.extend(env) - - # entry_points - paths.extend(_entry_point_paths(JUPYTER_CONFIG_PATH_ENTRY_POINT)) + paths.extend(entry_points) # Finally, system path paths.extend(SYSTEM_CONFIG_PATH) From 7e17498f63369378c83834097b9863c48c3c8950 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 3 Mar 2021 22:16:41 -0800 Subject: [PATCH 04/50] Add an example of a data file entry point. --- .../jupyter_path_entrypoint/src/entry_point_example/__init__.py | 1 + .../src/entry_point_example/share/jupyter/example_file.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py b/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py index 4243e3e..e4b1e5d 100644 --- a/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py +++ b/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py @@ -6,3 +6,4 @@ HERE = os.path.abspath(os.path.dirname(__file__)) JUPYTER_CONFIG = [os.path.join(HERE, "etc", "jupyter")] +JUPYTER_DATA = [os.path.join(HERE, "share", "jupyter")] diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json b/examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json @@ -0,0 +1 @@ +{} From 19135374ed46b38f0eafe32712f60df60e8dd486 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:21:33 -0500 Subject: [PATCH 05/50] add flit example --- docs/changelog.rst | 13 ++++ .../COPYING.md | 0 .../README.md | 0 .../pyproject.toml | 15 +++++ .../src/entry_point_example_flit}/__init__.py | 4 +- .../entrypoint-example-flit.json | 5 ++ .../share/jupyter/example_file_flit.json} | 0 .../COPYING.md | 61 +++++++++++++++++++ .../MANIFEST.in | 0 .../README.md | 4 ++ .../setup.cfg | 7 +-- .../setup.py | 0 .../__init__.py | 9 +++ .../jupyter_config.d/entrypoint-example.json | 0 .../jupyter/example_file_setuptools.json | 1 + 15 files changed, 113 insertions(+), 6 deletions(-) rename examples/{jupyter_path_entrypoint => jupyter_path_entrypoint_flit}/COPYING.md (100%) rename examples/{jupyter_path_entrypoint => jupyter_path_entrypoint_flit}/README.md (100%) create mode 100644 examples/jupyter_path_entrypoint_flit/pyproject.toml rename examples/{jupyter_path_entrypoint/src/entry_point_example => jupyter_path_entrypoint_flit/src/entry_point_example_flit}/__init__.py (53%) create mode 100644 examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json rename examples/{jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json => jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json} (100%) create mode 100644 examples/jupyter_path_entrypoint_setuptools/COPYING.md rename examples/{jupyter_path_entrypoint => jupyter_path_entrypoint_setuptools}/MANIFEST.in (100%) create mode 100644 examples/jupyter_path_entrypoint_setuptools/README.md rename examples/{jupyter_path_entrypoint => jupyter_path_entrypoint_setuptools}/setup.cfg (80%) rename examples/{jupyter_path_entrypoint => jupyter_path_entrypoint_setuptools}/setup.py (100%) create mode 100644 examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py rename examples/{jupyter_path_entrypoint/src/entry_point_example => jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools}/etc/jupyter/jupyter_config.d/entrypoint-example.json (100%) create mode 100644 examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json diff --git a/docs/changelog.rst b/docs/changelog.rst index 935eedb..cbc0dd3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,19 @@ Changes in jupyter-core ======================= +4.8 +--- + +4.8.0 +~~~~~ + +`on +GitHub `__ + +- Add new ``jupyter_data_paths`` and ``jupyter_config_paths`` ``entry_points`` + (:ghpull:`209`) to allow python packages to extend the data and config paths + in ``jupyter --paths``. These paths are considered immediately after those put + in-place with ``data_files`` but work with modern packaging tools. 4.7 --- diff --git a/examples/jupyter_path_entrypoint/COPYING.md b/examples/jupyter_path_entrypoint_flit/COPYING.md similarity index 100% rename from examples/jupyter_path_entrypoint/COPYING.md rename to examples/jupyter_path_entrypoint_flit/COPYING.md diff --git a/examples/jupyter_path_entrypoint/README.md b/examples/jupyter_path_entrypoint_flit/README.md similarity index 100% rename from examples/jupyter_path_entrypoint/README.md rename to examples/jupyter_path_entrypoint_flit/README.md diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml new file mode 100644 index 0000000..0aa2761 --- /dev/null +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["flit_core >=2,<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.metadata] +module = "entry_point_example_flit" +author = "Sir Robin" +author-email = "robin@camelot.uk" +home-page = "https://github.com/sirrobin/foobar" + +[tool.flit.entrypoints.jupyter_config_paths] +entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" + +[tool.flit.entrypoints.jupyter_data_paths] +entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py similarity index 53% rename from examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py index e4b1e5d..8d95ec5 100644 --- a/examples/jupyter_path_entrypoint/src/entry_point_example/__init__.py +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py @@ -5,5 +5,5 @@ HERE = os.path.abspath(os.path.dirname(__file__)) -JUPYTER_CONFIG = [os.path.join(HERE, "etc", "jupyter")] -JUPYTER_DATA = [os.path.join(HERE, "share", "jupyter")] +JUPYTER_CONFIG_PATHS = [os.path.join(HERE, "etc", "jupyter")] +JUPYTER_DATA_PATHS = [os.path.join(HERE, "share", "jupyter")] diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json new file mode 100644 index 0000000..081664f --- /dev/null +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json @@ -0,0 +1,5 @@ +{ + "SingletonConfigurable": { + "log_level": "INFO" + } +} diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint/src/entry_point_example/share/jupyter/example_file.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json diff --git a/examples/jupyter_path_entrypoint_setuptools/COPYING.md b/examples/jupyter_path_entrypoint_setuptools/COPYING.md new file mode 100644 index 0000000..e5ae344 --- /dev/null +++ b/examples/jupyter_path_entrypoint_setuptools/COPYING.md @@ -0,0 +1,61 @@ +# The Jupyter licensing terms + +Jupyter is licensed under the terms of the Modified BSD License (also known as +New or Revised or 3-Clause BSD), as follows: + +- Copyright (c) 2015-, Jupyter Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the Jupyter Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## About the Jupyter Development Team + +The Jupyter Development Team is the set of all contributors to the Jupyter +project. This includes all of the Jupyter subprojects. A full list with +details is kept in the documentation directory, in the file +`about/credits.txt`. + +The core team that coordinates development on GitHub can be found here: +https://github.com/ipython/. + +## Our Copyright Policy + +Jupyter uses a shared copyright model. Each contributor maintains copyright +over their contributions to Jupyter. It is important to note that these +contributions are typically only changes to the repositories. Thus, the Jupyter +source code in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire Jupyter +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the Jupyter repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + + # Copyright (c) Jupyter Development Team. + # Distributed under the terms of the Modified BSD License. diff --git a/examples/jupyter_path_entrypoint/MANIFEST.in b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in similarity index 100% rename from examples/jupyter_path_entrypoint/MANIFEST.in rename to examples/jupyter_path_entrypoint_setuptools/MANIFEST.in diff --git a/examples/jupyter_path_entrypoint_setuptools/README.md b/examples/jupyter_path_entrypoint_setuptools/README.md new file mode 100644 index 0000000..70f383b --- /dev/null +++ b/examples/jupyter_path_entrypoint_setuptools/README.md @@ -0,0 +1,4 @@ +# entry_point_example + +A minimal example of a package which provides an additional `jupyter_path` value +via `entry_point`. diff --git a/examples/jupyter_path_entrypoint/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg similarity index 80% rename from examples/jupyter_path_entrypoint/setup.cfg rename to examples/jupyter_path_entrypoint_setuptools/setup.cfg index 0bab959..cbd1098 100644 --- a/examples/jupyter_path_entrypoint/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -34,7 +34,6 @@ where = [options.entry_points] jupyter_config_paths = - entry-point-example = entry_point_example:JUPYTER_CONFIG -# similarly, adding a data path would be like -# jupyter_data_paths = -# entry-point-example = entry_point_example:JUPYTER_DATA + entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_CONFIG_PATHS +jupyter_data_paths = + entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATHS diff --git a/examples/jupyter_path_entrypoint/setup.py b/examples/jupyter_path_entrypoint_setuptools/setup.py similarity index 100% rename from examples/jupyter_path_entrypoint/setup.py rename to examples/jupyter_path_entrypoint_setuptools/setup.py diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py new file mode 100644 index 0000000..8d95ec5 --- /dev/null +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -0,0 +1,9 @@ +"""an example of using the jupyter_paths entry_point""" +import os + +__version__ = "0.1.0" + +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")] diff --git a/examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter/jupyter_config.d/entrypoint-example.json similarity index 100% rename from examples/jupyter_path_entrypoint/src/entry_point_example/etc/jupyter/jupyter_config.d/entrypoint-example.json rename to examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter/jupyter_config.d/entrypoint-example.json diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json @@ -0,0 +1 @@ +{} From b88bb49a74d05769ac37d0bf6eaf893cb1d5228e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:23:11 -0500 Subject: [PATCH 06/50] flatten flit example --- .../__init__.py => entry_point_example_flit.py} | 0 .../etc/jupyter/jupyter_config.d/entrypoint-example-flit.json | 0 .../share/jupyter/example_file_flit.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/jupyter_path_entrypoint_flit/{src/entry_point_example_flit/__init__.py => entry_point_example_flit.py} (100%) rename examples/jupyter_path_entrypoint_flit/{src/entry_point_example_flit => }/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json (100%) rename examples/jupyter_path_entrypoint_flit/{src/entry_point_example_flit => }/share/jupyter/example_file_flit.json (100%) diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py b/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py rename to examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json b/examples/jupyter_path_entrypoint_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json rename to examples/jupyter_path_entrypoint_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json b/examples/jupyter_path_entrypoint_flit/share/jupyter/example_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json rename to examples/jupyter_path_entrypoint_flit/share/jupyter/example_file_flit.json From cfe2aa1387a164b09f8808f5a748050dedafd787 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:28:16 -0500 Subject: [PATCH 07/50] try more flit conf --- examples/jupyter_path_entrypoint_flit/pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 0aa2761..a68104a 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -13,3 +13,7 @@ entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" [tool.flit.entrypoints.jupyter_data_paths] entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" + +[tool.flit.sdist] +include = ["etc/", "share/"] +# exclude = ["share/**/build_log.json"] From 709ddf182c7201df638ca836ab8eb77495d7a339 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:30:23 -0500 Subject: [PATCH 08/50] try more flit conf --- examples/jupyter_path_entrypoint_flit/pyproject.toml | 2 +- .../share/jupyter/example_excluded_file.json | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index a68104a..09874a8 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -16,4 +16,4 @@ entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" [tool.flit.sdist] include = ["etc/", "share/"] -# exclude = ["share/**/build_log.json"] +exclude = ["share/*/example_excluded_file.json"] diff --git a/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json b/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json new file mode 100644 index 0000000..e69de29 From f8fded88220e80a0f6a42e4cb7ec7515f1fe29d3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:35:51 -0500 Subject: [PATCH 09/50] more clean up --- .../jupyter_path_entrypoint_flit/entry_point_example_flit.py | 2 +- examples/jupyter_path_entrypoint_flit/pyproject.toml | 2 +- .../share/jupyter/example_excluded_file.json | 0 .../share/jupyter/example_excluded_file_flit.json | 1 + examples/jupyter_path_entrypoint_setuptools/MANIFEST.in | 1 + .../src/entry_point_example_setuptools/__init__.py | 2 +- .../share/jupyter/example_excluded_file_setuptools.json | 1 + 7 files changed, 6 insertions(+), 3 deletions(-) delete mode 100644 examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json create mode 100644 examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json create mode 100644 examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json diff --git a/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py b/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py index 8d95ec5..e354b92 100644 --- a/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py +++ b/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py @@ -1,4 +1,4 @@ -"""an example of using the jupyter_paths entry_point""" +"""an example of using the jupyter_*_paths entry_points with flit""" import os __version__ = "0.1.0" diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 09874a8..f5a082c 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -16,4 +16,4 @@ entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" [tool.flit.sdist] include = ["etc/", "share/"] -exclude = ["share/*/example_excluded_file.json"] +exclude = ["share/*/example_excluded_file_flit.json"] diff --git a/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json b/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file.json deleted file mode 100644 index e69de29..0000000 diff --git a/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json b/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json @@ -0,0 +1 @@ +[] diff --git a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in index 9e77b3e..9867231 100644 --- a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in +++ b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in @@ -1 +1,2 @@ recursive-include src/entry_point_example *.* +exclude src/entry_point_example/share/jupyter/example_excluded_file_setuptools.json diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py index 8d95ec5..64013c8 100644 --- a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -1,4 +1,4 @@ -"""an example of using the jupyter_paths entry_point""" +"""an example of using the jupyter_*_paths entry_points in setuptools""" import os __version__ = "0.1.0" diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json @@ -0,0 +1 @@ +[] From b26db468e46b56c12417937bdbf3589461da5ca6 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:38:58 -0500 Subject: [PATCH 10/50] clean up docs --- examples/jupyter_path_entrypoint_flit/README.md | 6 +++--- examples/jupyter_path_entrypoint_setuptools/README.md | 6 +++--- examples/jupyter_path_entrypoint_setuptools/setup.cfg | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/jupyter_path_entrypoint_flit/README.md b/examples/jupyter_path_entrypoint_flit/README.md index 70f383b..bd1e50b 100644 --- a/examples/jupyter_path_entrypoint_flit/README.md +++ b/examples/jupyter_path_entrypoint_flit/README.md @@ -1,4 +1,4 @@ -# entry_point_example +# entry_point_example_flit -A minimal example of a package which provides an additional `jupyter_path` value -via `entry_point`. +A minimal example of a package which provides additional `jupyter_*_path`s +via `entry_points`, packaged with `flit`. diff --git a/examples/jupyter_path_entrypoint_setuptools/README.md b/examples/jupyter_path_entrypoint_setuptools/README.md index 70f383b..ed9ad1c 100644 --- a/examples/jupyter_path_entrypoint_setuptools/README.md +++ b/examples/jupyter_path_entrypoint_setuptools/README.md @@ -1,4 +1,4 @@ -# entry_point_example +# entry_point_example_setuptools -A minimal example of a package which provides an additional `jupyter_path` value -via `entry_point`. +A minimal example of a package which provides additional `jupyter_*_path`s +via `entry_points`, packaged with `setuptools`. diff --git a/examples/jupyter_path_entrypoint_setuptools/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg index cbd1098..71239d8 100644 --- a/examples/jupyter_path_entrypoint_setuptools/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -1,6 +1,6 @@ [metadata] -name = entry_point_example -version = attr: entry_point_example.__version__ +name = entry_point_example_setuptools +version = attr: entry_point_example_setuptools.__version__ description = a dummy module for testing jupyter_paths entrypoint long_description = file: README.md long_description_content_type = text/markdown From 308086c15e9be3665eaa780b04ae7d5f1ee0e6de Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 09:41:06 -0500 Subject: [PATCH 11/50] more docs changelog --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index cbc0dd3..ffea690 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,7 +13,7 @@ GitHub `__ - Add new ``jupyter_data_paths`` and ``jupyter_config_paths`` ``entry_points`` (:ghpull:`209`) to allow python packages to extend the data and config paths in ``jupyter --paths``. These paths are considered immediately after those put - in-place with ``data_files`` but work with modern packaging tools. + in-place with ``data_files``, but work with modern python packaging tools. 4.7 --- From 5fbe54317bdbd6914c40824c6c5acfca401b60da Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 10:15:03 -0500 Subject: [PATCH 12/50] add tests --- jupyter_core/paths.py | 14 ++++++------ jupyter_core/tests/test_paths.py | 37 ++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 0280abf..4ec3668 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -15,11 +15,12 @@ import tempfile import warnings import traceback -import entrypoints -from functools import lru_cache from contextlib import contextmanager +import entrypoints + + pjoin = os.path.join # UF_HIDDEN is a stat flag not defined in the stat module. @@ -32,16 +33,17 @@ JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_paths" JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_paths" -@lru_cache(maxsize=10) + def _entry_point_paths(ep_group): """Load extra jupyter paths from entry_points, sorted by their entry_point name """ paths = [] for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): try: - paths.extend(ep.load()) - except: - warnings.warn('Failed to load jupyter_paths from entry_point "{}"\n{}'.format( + paths.extend([*map(str, ep.load())]) + except Exception: + warnings.warn('Failed to load {} from entry_point "{}"\n{}'.format( + ep_group, name, traceback.format_exc() )) diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 6d37d22..5692c10 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -8,15 +8,18 @@ import stat import shutil import tempfile -from unittest.mock import patch +from unittest.mock import patch, Mock import pytest import sys +import entrypoints + from jupyter_core import paths from jupyter_core.paths import ( jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir, jupyter_path, jupyter_config_path, ENV_JUPYTER_PATH, - secure_write, is_hidden, is_file_hidden + secure_write, is_hidden, is_file_hidden, JUPYTER_CONFIG_PATH_ENTRY_POINT, + JUPYTER_DATA_PATH_ENTRY_POINT ) from .mocking import darwin, windows, linux @@ -334,3 +337,33 @@ def test_secure_write_unix(): assert f.read() == 'test 2' finally: shutil.rmtree(directory) + + +@pytest.fixture +def data_path_entry_point(): + ep = Mock(spec=['load']) + ep.name = JUPYTER_DATA_PATH_ENTRY_POINT + ep.load.return_value = ['/foo/share'] + + with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): + yield ep + + +def test_data_entry_point(data_path_entry_point): + data_path = jupyter_path() + assert '/foo/share' in data_path + + +@pytest.fixture +def config_path_entry_point(): + ep = Mock(spec=['load']) + ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT + ep.load.return_value = ['/foo/etc'] + + with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): + yield ep + + +def test_config_entry_point(config_path_entry_point): + config_path = jupyter_config_path() + assert '/foo/etc' in config_path From 5044183dbe8d4332b6a5e3e5f042589ef164dc01 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 10:23:34 -0500 Subject: [PATCH 13/50] flesh out development options --- .../jupyter_path_entrypoint_flit/README.md | 35 +++++++++++++++++++ .../README.md | 18 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/examples/jupyter_path_entrypoint_flit/README.md b/examples/jupyter_path_entrypoint_flit/README.md index bd1e50b..2ee7cf8 100644 --- a/examples/jupyter_path_entrypoint_flit/README.md +++ b/examples/jupyter_path_entrypoint_flit/README.md @@ -2,3 +2,38 @@ A minimal example of a package which provides additional `jupyter_*_path`s via `entry_points`, packaged with `flit`. + +## Developing + +### Setup + +- ensure `flit` is installed + +### Editable install + +On any platform, enable live development by putting a `.pth` file in `site-packages`: + +```bash +flit install --pth-file +``` + +On UNIX, a symlink may be used instead: + +```bash +flint install --symlink +``` + +### Building + +```bash +flit build +``` + +Optionally, set the `SOURCE_DATE_EPOCH` environment variable to ensure a +[reproducible](https://reproducible-builds.org) `.whl`. + +> For example, to use the last `git` commit: +> +> ```bash +> SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) flit build +> ``` diff --git a/examples/jupyter_path_entrypoint_setuptools/README.md b/examples/jupyter_path_entrypoint_setuptools/README.md index ed9ad1c..90cca80 100644 --- a/examples/jupyter_path_entrypoint_setuptools/README.md +++ b/examples/jupyter_path_entrypoint_setuptools/README.md @@ -2,3 +2,21 @@ A minimal example of a package which provides additional `jupyter_*_path`s via `entry_points`, packaged with `setuptools`. + +## Developing + +### Setup + +- ensure `pip` is installed + +### Editable install + +```bash +pip install -e . +``` + +### Building + +```bash +python setup.py sdist bdist_wheel +``` From f3c4ee5aac6797e0aa7d6ac0e25d05ee54c142b0 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 10:53:55 -0500 Subject: [PATCH 14/50] working on ci --- .github/workflows/test.yml | 146 ++++++++++++++---- .../MANIFEST.in | 4 +- jupyter_core/paths.py | 1 + setup.cfg | 6 + 4 files changed, 125 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2191cf9..b3b8a9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,38 +9,124 @@ on: pull_request: branches: [ master ] +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + + +defaults: + run: + shell: bash -l {0} + jobs: - build: + dist: + runs-on: ubuntu-20.04 + strategy: + matrix: + os: [linux] + python-version: [3.9] + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Upgrade packaging deps + run: | + set -eux + python -m pip install --upgrade --user pip wheel setuptools + + - name: Build distributions + run: | + set -eux + python setup.py sdist bdist_wheel + cd dist + sha256sum * | tee SHA256SUMS + + - name: publish dists + uses: actions/upload-artifact@v2 + with: + name: jupyter_core dist ${{ github.run_number }} + path: ./dist - runs-on: ${{ matrix.os }} + test: + needs: [dist] + runs-on: ${{ matrix.vm }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: [3.6, 3.7, 3.8, 3.9] + os: [linux, windows, macos] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + include: + - os: linux + vm: ubuntu-20.04 + py_cmd: python + - os: macos + vm: macos-latest + py_cmd: python3 + - os: windows + vm: windows-latest + py_cmd: python + # different dists + - python-version: 3.6 + dist: 'jupyter-core*.tar.gz' + - python-version: 3.7 + dist: 'jupyter_core*.whl' + - python-version: 3.8 + dist: 'jupyter-core*.tar.gz' + - python-version: 3.9 + dist: 'jupyter_core*.whl' + - python-version: pypy + dist: 'jupyter-core*.tar.gz' + exclude: + - os: windows + python-version: pypy3 steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: pip cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('dev-requirements.txt', 'setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --upgrade setuptools - pip install --upgrade nose - pip install -r dev-requirements.txt . - - name: Test with pytest - run: | - pytest jupyter_core + - uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: jupyter_core dist ${{ github.run_number }} + path: ./dist + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Upgrade packaging deps + run: | + ${{ matrix.py_cmd }} -m pip install --upgrade pip wheel setuptools + + - name: Install dist + run: | + ${{ matrix.py_cmd }} -m pip install -vv ${{ matrix.dist }} + ${{ matrix.py_cmd }} -m pip check + + - name: Install test dependencies + run: | + ${{ matrix.py_cmd }} -m pip install -r dev-requirements.txt codecov + ${{ matrix.py_cmd }} -m pip check + + - name: Test with pytest + run: | + ${{ matrix.py_cmd }} -m pytest -vv --ff --pyargs=jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail + + - name: Upload coverage + if: ${{ matrix.python-version != 'pypy3' }} + run: | + ${{ matrix.py_cmd }} -m codecov + + - name: Test setuptools example + run: | + cd examples/jupyter_path_entrypoint_setuptools + ${{ matrix.py_cmd }} -m pip install -e . + jupyter_paths | tee paths + cat paths.json | grep jupyter_path_entrypoint_setuptools + + - name: Test flit example + run: | + cd examples/jupyter_path_entrypoint_flit + ${{ matrix.py_cmd }} -m pip install -e . + jupyter_paths | tee paths + cat paths.json | grep jupyter_path_entrypoint_flit diff --git a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in index 9867231..f10e0f7 100644 --- a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in +++ b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in @@ -1,2 +1,2 @@ -recursive-include src/entry_point_example *.* -exclude src/entry_point_example/share/jupyter/example_excluded_file_setuptools.json +recursive-include src/entry_point_example_setuptools *.* +exclude src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 4ec3668..5d76da7 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -38,6 +38,7 @@ def _entry_point_paths(ep_group): """Load extra jupyter paths from entry_points, sorted by their entry_point name """ paths = [] + raise Exception(ep_group) for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): try: paths.extend([*map(str, ep.load())]) diff --git a/setup.cfg b/setup.cfg index 5f843c6..a065370 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,12 @@ install_requires = entrypoints pywin32>=1.0 ; sys_platform == 'win32' +[options.extras_require] +test = + ipykernel + pytest + pytest-cov + [options.entry_points] console_scripts = jupyter = jupyter_core.command:main From 1faf4ab8ec83b0bf6fe9d31776c19cbbf859accf Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:02:01 -0500 Subject: [PATCH 15/50] clean up ci test --- .github/workflows/test.yml | 11 +++++++---- jupyter_core/paths.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3b8a9e..80896f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,12 +121,15 @@ jobs: run: | cd examples/jupyter_path_entrypoint_setuptools ${{ matrix.py_cmd }} -m pip install -e . - jupyter_paths | tee paths - cat paths.json | grep jupyter_path_entrypoint_setuptools + ${{ matrix.py_cmd }} -m jupyter --paths | tee paths.txt + cat paths.txt | grep jupyter_path_entrypoint_setuptools/share/jupyter + cat paths.txt | grep jupyter_path_entrypoint_setuptools/etc/jupyter - name: Test flit example run: | + ${{ matrix.py_cmd }} -m pip install flit cd examples/jupyter_path_entrypoint_flit ${{ matrix.py_cmd }} -m pip install -e . - jupyter_paths | tee paths - cat paths.json | grep jupyter_path_entrypoint_flit + ${{ matrix.py_cmd }} -m jupyter --paths | tee paths.txt + cat paths.txt | grep jupyter_path_entrypoint_flit/share/jupyter + cat paths.txt | grep jupyter_path_entrypoint_flit/etc/jupyter diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 5d76da7..4ec3668 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -38,7 +38,6 @@ def _entry_point_paths(ep_group): """Load extra jupyter paths from entry_points, sorted by their entry_point name """ paths = [] - raise Exception(ep_group) for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): try: paths.extend([*map(str, ep.load())]) From cd9ab14212ee52d9036b1f371ac7729f4b452e11 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:06:34 -0500 Subject: [PATCH 16/50] more work on ci --- .github/workflows/test.yml | 42 +++++++++++++++++--------------------- MANIFEST.in | 1 + 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80896f3..5de8875 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,19 +18,15 @@ defaults: shell: bash -l {0} jobs: - dist: + build: runs-on: ubuntu-20.04 - strategy: - matrix: - os: [linux] - python-version: [3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.9 - name: Upgrade packaging deps run: | @@ -51,22 +47,22 @@ jobs: path: ./dist test: - needs: [dist] + needs: [build] runs-on: ${{ matrix.vm }} strategy: matrix: - os: [linux, windows, macos] + os: [linux, macos, windows] python-version: [3.6, 3.7, 3.8, 3.9, pypy3] include: - os: linux vm: ubuntu-20.04 - py_cmd: python + py-cmd: python - os: macos vm: macos-latest - py_cmd: python3 + py-cmd: python3 - os: windows vm: windows-latest - py_cmd: python + py-cmd: python # different dists - python-version: 3.6 dist: 'jupyter-core*.tar.gz' @@ -96,40 +92,40 @@ jobs: - name: Upgrade packaging deps run: | - ${{ matrix.py_cmd }} -m pip install --upgrade pip wheel setuptools + ${{ matrix.py-cmd }} -m pip install --upgrade pip wheel setuptools - name: Install dist run: | - ${{ matrix.py_cmd }} -m pip install -vv ${{ matrix.dist }} - ${{ matrix.py_cmd }} -m pip check + ${{ matrix.py-cmd }} -m pip install -vv ${{ matrix.dist }} + ${{ matrix.py-cmd }} -m pip check - name: Install test dependencies run: | - ${{ matrix.py_cmd }} -m pip install -r dev-requirements.txt codecov - ${{ matrix.py_cmd }} -m pip check + ${{ matrix.py-cmd }} -m pip install -r dev-requirements.txt codecov + ${{ matrix.py-cmd }} -m pip check - name: Test with pytest run: | - ${{ matrix.py_cmd }} -m pytest -vv --ff --pyargs=jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs=jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail - name: Upload coverage if: ${{ matrix.python-version != 'pypy3' }} run: | - ${{ matrix.py_cmd }} -m codecov + ${{ matrix.py-cmd }} -m codecov - name: Test setuptools example run: | cd examples/jupyter_path_entrypoint_setuptools - ${{ matrix.py_cmd }} -m pip install -e . - ${{ matrix.py_cmd }} -m jupyter --paths | tee paths.txt + ${{ matrix.py-cmd }} -m pip install -e . + ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt cat paths.txt | grep jupyter_path_entrypoint_setuptools/share/jupyter cat paths.txt | grep jupyter_path_entrypoint_setuptools/etc/jupyter - name: Test flit example run: | - ${{ matrix.py_cmd }} -m pip install flit + ${{ matrix.py-cmd }} -m pip install flit cd examples/jupyter_path_entrypoint_flit - ${{ matrix.py_cmd }} -m pip install -e . - ${{ matrix.py_cmd }} -m jupyter --paths | tee paths.txt + ${{ matrix.py-cmd }} -m pip install -e . + ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt cat paths.txt | grep jupyter_path_entrypoint_flit/share/jupyter cat paths.txt | grep jupyter_path_entrypoint_flit/etc/jupyter diff --git a/MANIFEST.in b/MANIFEST.in index d9dada4..f20cb3e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,6 +13,7 @@ graft jupyter_core/tests/dotipython_empty # docs subdirs we want to skip prune docs/_build +prune examples # Patterns to exclude from any directory global-exclude *~ From d95e9ee2da33e3e9cd5c80e73337001ba4b1f874 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:09:09 -0500 Subject: [PATCH 17/50] just deal with ubunut-latest warn for now --- .github/workflows/test.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5de8875..eb47230 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,7 @@ on: env: PIP_DISABLE_PIP_VERSION_CHECK: 1 + CACHE_EPOCH: 0 defaults: @@ -48,20 +49,17 @@ jobs: test: needs: [build] - runs-on: ${{ matrix.vm }} + runs-on: ${{ matrix.os }}-latest strategy: matrix: os: [linux, macos, windows] python-version: [3.6, 3.7, 3.8, 3.9, pypy3] include: - os: linux - vm: ubuntu-20.04 py-cmd: python - os: macos - vm: macos-latest py-cmd: python3 - os: windows - vm: windows-latest py-cmd: python # different dists - python-version: 3.6 @@ -94,14 +92,9 @@ jobs: run: | ${{ matrix.py-cmd }} -m pip install --upgrade pip wheel setuptools - - name: Install dist + - name: Install distribution and test dependencies run: | - ${{ matrix.py-cmd }} -m pip install -vv ${{ matrix.dist }} - ${{ matrix.py-cmd }} -m pip check - - - name: Install test dependencies - run: | - ${{ matrix.py-cmd }} -m pip install -r dev-requirements.txt codecov + ${{ matrix.py-cmd }} -m pip install -vv ${{ matrix.dist }} -r dev-requirements.txt codecov ${{ matrix.py-cmd }} -m pip check - name: Test with pytest From 1293714eef6be8f8d52163cbb8a30bc6c84f6622 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:10:25 -0500 Subject: [PATCH 18/50] fix pypy excursion --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb47230..cde5c92 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: dist: 'jupyter-core*.tar.gz' - python-version: 3.9 dist: 'jupyter_core*.whl' - - python-version: pypy + - python-version: pypy3 dist: 'jupyter-core*.tar.gz' exclude: - os: windows From ebf260c3163008984575ab17c2a827c778ef540b Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:12:01 -0500 Subject: [PATCH 19/50] fix linux name --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cde5c92..0e2fb5f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,10 +52,10 @@ jobs: runs-on: ${{ matrix.os }}-latest strategy: matrix: - os: [linux, macos, windows] + os: [ubuntu, macos, windows] python-version: [3.6, 3.7, 3.8, 3.9, pypy3] include: - - os: linux + - os: ubuntu py-cmd: python - os: macos py-cmd: python3 From 41e206b6c0c51b526788cdc04c51e92c965d86ee Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:15:13 -0500 Subject: [PATCH 20/50] use subshell to get dist --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e2fb5f..f3675c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,20 +94,24 @@ jobs: - name: Install distribution and test dependencies run: | - ${{ matrix.py-cmd }} -m pip install -vv ${{ matrix.dist }} -r dev-requirements.txt codecov + set -eux + ${{ matrix.py-cmd }} -m pip install -vv $(${{ matrix.dist }}) -r dev-requirements.txt codecov ${{ matrix.py-cmd }} -m pip check - name: Test with pytest run: | + set -eux ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs=jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail - name: Upload coverage if: ${{ matrix.python-version != 'pypy3' }} run: | + set -eux ${{ matrix.py-cmd }} -m codecov - name: Test setuptools example run: | + set -eux cd examples/jupyter_path_entrypoint_setuptools ${{ matrix.py-cmd }} -m pip install -e . ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt @@ -116,6 +120,7 @@ jobs: - name: Test flit example run: | + set -eux ${{ matrix.py-cmd }} -m pip install flit cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m pip install -e . From 596e47015f648d4c2e130016c0277f21b2d39d91 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:17:12 -0500 Subject: [PATCH 21/50] pyargs doesn't take a value --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3675c6..25cc091 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -101,7 +101,7 @@ jobs: - name: Test with pytest run: | set -eux - ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs=jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail - name: Upload coverage if: ${{ matrix.python-version != 'pypy3' }} From 2615f9055d3fa2517705c1f078a20af56831c361 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:19:43 -0500 Subject: [PATCH 22/50] clean up ci file --- .github/workflows/test.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25cc091..2b3e2f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,8 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions +# This workflow will install Python dependencies, run tests and lint with a +# variety of Python versions +# +# For more information see: +# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Python package @@ -11,8 +14,6 @@ on: env: PIP_DISABLE_PIP_VERSION_CHECK: 1 - CACHE_EPOCH: 0 - defaults: run: @@ -95,13 +96,19 @@ jobs: - name: Install distribution and test dependencies run: | set -eux - ${{ matrix.py-cmd }} -m pip install -vv $(${{ matrix.dist }}) -r dev-requirements.txt codecov + ${{ matrix.py-cmd }} -m pip install -vv \ + $(${{ matrix.dist }}) \ + -r dev-requirements.txt codecov ${{ matrix.py-cmd }} -m pip check - name: Test with pytest run: | set -eux - ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core --cov=jupyter_core --cov-report=term-missing:skip-covered --cov-report=html --no-cov-on-fail + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ + --cov=jupyter_core \ + --cov-report=term-missing:skip-covered \ + --cov-report=html \ + --no-cov-on-fail - name: Upload coverage if: ${{ matrix.python-version != 'pypy3' }} From 2235c95a9e0b9292025a37c8422f885c45608e31 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:22:34 -0500 Subject: [PATCH 23/50] add pytest-cov --- dev-requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 4ccaa98..2f6ffa2 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,3 @@ -pytest ipykernel +pytest +pytest-cov From 5d769d58b3ba9673d63f960461a60d1b69519668 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:25:46 -0500 Subject: [PATCH 24/50] defer codecov --- .github/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b3e2f7..c5b4d19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,9 +96,10 @@ jobs: - name: Install distribution and test dependencies run: | set -eux - ${{ matrix.py-cmd }} -m pip install -vv \ + ${{ matrix.py-cmd }} -m pip install \ $(${{ matrix.dist }}) \ - -r dev-requirements.txt codecov + -r dev-requirements.txt + ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check - name: Test with pytest @@ -114,6 +115,7 @@ jobs: if: ${{ matrix.python-version != 'pypy3' }} run: | set -eux + ${{ matrix.py-cmd }} -m pip install -vv codecov ${{ matrix.py-cmd }} -m codecov - name: Test setuptools example From 73f983eba98fb5c647f1c140a302a9b493e6fe9f Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:29:20 -0500 Subject: [PATCH 25/50] run tests in dist folder --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5b4d19..5662572 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,7 +97,7 @@ jobs: run: | set -eux ${{ matrix.py-cmd }} -m pip install \ - $(${{ matrix.dist }}) \ + $(ls dist/${{ matrix.dist }}) \ -r dev-requirements.txt ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check @@ -105,6 +105,7 @@ jobs: - name: Test with pytest run: | set -eux + cd dist ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ --cov=jupyter_core \ --cov-report=term-missing:skip-covered \ From ecc225058bb34773d8dbceec1caf9919be7be601 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:32:32 -0500 Subject: [PATCH 26/50] fix entry_point check --- .github/workflows/test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5662572..8353d10 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,6 +116,7 @@ jobs: if: ${{ matrix.python-version != 'pypy3' }} run: | set -eux + cd dist ${{ matrix.py-cmd }} -m pip install -vv codecov ${{ matrix.py-cmd }} -m codecov @@ -125,8 +126,8 @@ jobs: cd examples/jupyter_path_entrypoint_setuptools ${{ matrix.py-cmd }} -m pip install -e . ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep jupyter_path_entrypoint_setuptools/share/jupyter - cat paths.txt | grep jupyter_path_entrypoint_setuptools/etc/jupyter + cat paths.txt | grep entry_point_example_setuptools/share/jupyter + cat paths.txt | grep entry_point_example_setuptools/etc/jupyter - name: Test flit example run: | @@ -135,5 +136,5 @@ jobs: cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m pip install -e . ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep jupyter_path_entrypoint_flit/share/jupyter - cat paths.txt | grep jupyter_path_entrypoint_flit/etc/jupyter + cat paths.txt | grep entry_point_example_flit/share/jupyter + cat paths.txt | grep entry_point_example_flit/etc/jupyter From 115337d45fec8b5c9010f85a1942c0142b100b5d Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:35:32 -0500 Subject: [PATCH 27/50] fix install from dist --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8353d10..c31a9ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,8 +96,9 @@ jobs: - name: Install distribution and test dependencies run: | set -eux + cd dist ${{ matrix.py-cmd }} -m pip install \ - $(ls dist/${{ matrix.dist }}) \ + $(ls ${{ matrix.dist }}) \ -r dev-requirements.txt ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check From 65f1d9d1f394c34c06ccc83f6062a580b42b925f Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:37:01 -0500 Subject: [PATCH 28/50] fix path to reqs file --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c31a9ff..2b3ca76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,7 +99,7 @@ jobs: cd dist ${{ matrix.py-cmd }} -m pip install \ $(ls ${{ matrix.dist }}) \ - -r dev-requirements.txt + -r ../dev-requirements.txt ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check From 30731c5a1639fd0d637a89bcfdab421a4b64751c Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:40:00 -0500 Subject: [PATCH 29/50] install tarball separatly --- .github/workflows/test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b3ca76..55e4398 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -97,9 +97,8 @@ jobs: run: | set -eux cd dist - ${{ matrix.py-cmd }} -m pip install \ - $(ls ${{ matrix.dist }}) \ - -r ../dev-requirements.txt + ${{ matrix.py-cmd }} -m pip install -r ../dev-requirements.txt + ${{ matrix.py-cmd }} -m pip install --ignore-installed $(ls ${{ matrix.dist }}) \ ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check From 02a88ce3815c991f205a3da582a3ffb91be695f5 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:40:59 -0500 Subject: [PATCH 30/50] fix dangling slash --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55e4398..5eadb41 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -98,7 +98,7 @@ jobs: set -eux cd dist ${{ matrix.py-cmd }} -m pip install -r ../dev-requirements.txt - ${{ matrix.py-cmd }} -m pip install --ignore-installed $(ls ${{ matrix.dist }}) \ + ${{ matrix.py-cmd }} -m pip install --ignore-installed $(ls ${{ matrix.dist }}) ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check From 22ae57c67280e386dd424348c74bfdecd8a7845a Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:43:59 -0500 Subject: [PATCH 31/50] no - in sdist --- .github/workflows/test.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5eadb41..2e5a4fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,15 +64,15 @@ jobs: py-cmd: python # different dists - python-version: 3.6 - dist: 'jupyter-core*.tar.gz' + dist: jupyter_core*.tar.gz - python-version: 3.7 - dist: 'jupyter_core*.whl' + dist: jupyter_core*.whl - python-version: 3.8 - dist: 'jupyter-core*.tar.gz' + dist: jupyter_core*.tar.gz - python-version: 3.9 - dist: 'jupyter_core*.whl' + dist: jupyter_core*.whl - python-version: pypy3 - dist: 'jupyter-core*.tar.gz' + dist: jupyter_core*.tar.gz exclude: - os: windows python-version: pypy3 @@ -97,6 +97,7 @@ jobs: run: | set -eux cd dist + ls ${{ matrix.py-cmd }} -m pip install -r ../dev-requirements.txt ${{ matrix.py-cmd }} -m pip install --ignore-installed $(ls ${{ matrix.dist }}) ${{ matrix.py-cmd }} -m pip freeze From 0f4fb834ba20ff274a8b233190f0ae807d4a7baa Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:46:18 -0500 Subject: [PATCH 32/50] install flit package with pth option --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e5a4fa..080e1ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,7 +135,7 @@ jobs: set -eux ${{ matrix.py-cmd }} -m pip install flit cd examples/jupyter_path_entrypoint_flit - ${{ matrix.py-cmd }} -m pip install -e . + ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt cat paths.txt | grep entry_point_example_flit/share/jupyter cat paths.txt | grep entry_point_example_flit/etc/jupyter From 4365f5a773474f008968e85ff9d831650b68527b Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:49:53 -0500 Subject: [PATCH 33/50] fix flit entry point --- examples/jupyter_path_entrypoint_flit/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index f5a082c..fa2f51c 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -12,7 +12,7 @@ home-page = "https://github.com/sirrobin/foobar" entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" [tool.flit.entrypoints.jupyter_data_paths] -entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" +entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATHS" [tool.flit.sdist] include = ["etc/", "share/"] From e55ee6a321895056ba69c2b1b106403f78e583bd Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:54:21 -0500 Subject: [PATCH 34/50] even simpler --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 080e1ee..b5e7904 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,5 +137,5 @@ jobs: cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep entry_point_example_flit/share/jupyter - cat paths.txt | grep entry_point_example_flit/etc/jupyter + cat paths.txt | grep jupyter_path_entrypoint_flit/share/jupyter + cat paths.txt | grep jupyter_path_entrypoint_flit/etc/jupyter From 38e3acd220153871ddd93d3e77a8b0af9e18c9db Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 11:58:27 -0500 Subject: [PATCH 35/50] windows paths --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b5e7904..d0a45c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -127,8 +127,8 @@ jobs: cd examples/jupyter_path_entrypoint_setuptools ${{ matrix.py-cmd }} -m pip install -e . ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep entry_point_example_setuptools/share/jupyter - cat paths.txt | grep entry_point_example_setuptools/etc/jupyter + cat paths.txt | grep entry_point_example_setuptools.share.jupyter + cat paths.txt | grep entry_point_example_setuptools.etc.jupyter - name: Test flit example run: | @@ -137,5 +137,5 @@ jobs: cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep jupyter_path_entrypoint_flit/share/jupyter - cat paths.txt | grep jupyter_path_entrypoint_flit/etc/jupyter + cat paths.txt | grep jupyter_path_entrypoint_flit.share.jupyter + cat paths.txt | grep jupyter_path_entrypoint_flit.etc.jupyter From b5dd3e032ecc3990f156b3de106fb82f33fc4e79 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 4 Mar 2021 21:06:57 -0500 Subject: [PATCH 36/50] compare relative indices in path tests --- jupyter_core/tests/test_paths.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 5692c10..1e9cd23 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -186,8 +186,8 @@ def test_jupyter_path(): def test_jupyter_path_prefer_env(): with patch.dict('os.environ', {'JUPYTER_PREFER_ENV_PATH': 'true'}): path = jupyter_path() - assert path[0] == paths.ENV_JUPYTER_PATH[0] - assert path[1] == jupyter_data_dir() + + assert path.index(paths.ENV_JUPYTER_PATH[0]) < path.index(jupyter_data_dir()) def test_jupyter_path_env(): path_env = os.pathsep.join([ @@ -197,7 +197,8 @@ def test_jupyter_path_env(): with patch.dict('os.environ', {'JUPYTER_PATH': path_env}): path = jupyter_path() - assert path[:2] == [pjoin('foo', 'bar'), pjoin('bar', 'baz')] + + assert path.index(pjoin('foo', 'bar')) == path.index(pjoin('bar', 'baz')) - 1 def test_jupyter_path_sys_prefix(): @@ -214,13 +215,13 @@ def test_jupyter_path_subdir(): def test_jupyter_config_path(): path = jupyter_config_path() assert path[0] == jupyter_config_dir() - assert path[1] == paths.ENV_CONFIG_PATH[0] + assert paths.ENV_CONFIG_PATH[0] in path def test_jupyter_config_path_prefer_env(): with patch.dict('os.environ', {'JUPYTER_PREFER_ENV_PATH': 'true'}): path = jupyter_config_path() assert path[0] == paths.ENV_CONFIG_PATH[0] - assert path[1] == jupyter_config_dir() + assert jupyter_config_dir() in path def test_jupyter_config_path_env(): path_env = os.pathsep.join([ From 05f77bd6de633d0c9614a1969f5b321a1318ff13 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 7 Mar 2021 12:12:50 -0500 Subject: [PATCH 37/50] add strategies for loading entry_points --- .../entry_point_example_flit.py | 9 -- .../pyproject.toml | 4 +- .../src/entry_point_example_flit/__init__.py | 5 + .../entrypoint-example-flit.json | 0 .../jupyter/example_excluded_file_flit.json | 0 .../share/jupyter/example_file_flit.json | 0 .../__init__.py | 11 +- jupyter_core/paths.py | 100 +++++++++++++++++- jupyter_core/tests/test_paths.py | 50 ++++++--- 9 files changed, 143 insertions(+), 36 deletions(-) delete mode 100644 examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py create mode 100644 examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py rename examples/jupyter_path_entrypoint_flit/{ => src/entry_point_example_flit}/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json (100%) rename examples/jupyter_path_entrypoint_flit/{ => src/entry_point_example_flit}/share/jupyter/example_excluded_file_flit.json (100%) rename examples/jupyter_path_entrypoint_flit/{ => src/entry_point_example_flit}/share/jupyter/example_file_flit.json (100%) diff --git a/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py b/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py deleted file mode 100644 index e354b92..0000000 --- a/examples/jupyter_path_entrypoint_flit/entry_point_example_flit.py +++ /dev/null @@ -1,9 +0,0 @@ -"""an example of using the jupyter_*_paths entry_points with flit""" -import os - -__version__ = "0.1.0" - -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")] diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index fa2f51c..0552304 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -15,5 +15,5 @@ entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATHS" [tool.flit.sdist] -include = ["etc/", "share/"] -exclude = ["share/*/example_excluded_file_flit.json"] +include = ["src/entry_point_example_flit/etc/", "src/entry_point_example_flit/share/"] +exclude = ["src/entry_point_example_flit/share/*/example_excluded_file_flit.json"] diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py new file mode 100644 index 0000000..86d014e --- /dev/null +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py @@ -0,0 +1,5 @@ +"""an example of using the jupyter_*_paths entry_points with flit""" +__version__ = "0.1.0" + +JUPYTER_CONFIG_PATHS = ["etc/jupyter"] +JUPYTER_DATA_PATHS = ["share/jupyter"] diff --git a/examples/jupyter_path_entrypoint_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json diff --git a/examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_excluded_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/share/jupyter/example_excluded_file_flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_excluded_file_flit.json diff --git a/examples/jupyter_path_entrypoint_flit/share/jupyter/example_file_flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/share/jupyter/example_file_flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py index 64013c8..e0cb6a6 100644 --- a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -1,9 +1,6 @@ -"""an example of using the jupyter_*_paths entry_points in setuptools""" -import os - +"""an example of using the jupyter_*_paths entry_points in setuptools +""" __version__ = "0.1.0" -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 = ["etc/jupyter"] +JUPYTER_DATA_PATHS = ["share/jupyter"] diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 4ec3668..bc1533c 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -9,12 +9,17 @@ import os +import ast import sys import stat import errno +import time +import pathlib import tempfile import warnings +import functools import traceback +import importlib from contextlib import contextmanager @@ -33,22 +38,109 @@ JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_paths" JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_paths" +# TODO: remove, once the correct strategy is decided +JUPYTER_ENTRY_POINT_STRATEGY = os.environ.get("JUPYTER_ENTRY_POINT_STRATEGY") +JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") -def _entry_point_paths(ep_group): - """Load extra jupyter paths from entry_points, sorted by their entry_point name + +# TODO: remove if not parsing +# from https://github.com/pypa/setuptools/blob/23ee037d56a6d8ab957882e1a041f67924ae04da/setuptools/config.py#L19 +class StaticModule: + """ + Attempt to load the module by the name """ + def __init__(self, name): + spec = importlib.util.find_spec(name) + + with open(spec.origin) as strm: + src = strm.read() + module = ast.parse(src) + vars(self).update(locals()) + del self.self + # add a path + self.__file__ = spec.origin + + def __getattr__(self, attr): + try: + return next( + ast.literal_eval(statement.value) + for statement in self.module.body + if isinstance(statement, ast.Assign) + for target in statement.targets + if isinstance(target, ast.Name) and target.id == attr + ) + except Exception as e: + raise AttributeError( + "{self.name} has no attribute {attr}".format(**locals()) + ) from e + +# TODO: remove if not parsing +@functools.lru_cache(maxsize=1024) +def _load_static_module(module_name): + return StaticModule(module_name) + + +def _load_paths_from_one_entry_point(ep): + """ get the paths from the entry_point target by importing + """ + loaded = ep.load() + spec = importlib.util.find_spec(ep.module_name) + module = importlib.util.module_from_spec(spec) + origin = pathlib.Path(module.__file__).parent.resolve() + return [str(origin / path) for path in loaded] + + +def _parse_paths_from_one_entry_point(ep): + """ get the paths from the AST of the entry_point target without importing + """ + static_mod = _load_static_module(ep.module_name) + raw_mod_paths = getattr(static_mod, ep.object_name, []) + origin = pathlib.Path(static_mod.__file__).parent.resolve() + return [str(origin / path) for path in raw_mod_paths] + + +def _parse_or_load_paths_from_one_entry_point(ep): + return _parse_paths_from_one_entry_point(ep) or _load_paths_from_one_entry_point(ep) + + +if JUPYTER_ENTRY_POINT_STRATEGY == "LOAD": + _get_paths_from_one_entry_point = _load_paths_from_one_entry_point +elif JUPYTER_ENTRY_POINT_STRATEGY == "PARSE": + _get_paths_from_one_entry_point = _parse_paths_from_one_entry_point +else: + _get_paths_from_one_entry_point = _parse_or_load_paths_from_one_entry_point + +def _entry_point_paths(ep_group): + start = time.time() + + group = reversed(sorted(entrypoints.get_group_named(ep_group).items())) + JUPYTER_ENTRY_POINT_TIMINGS and print( + f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded" + ) paths = [] - for name, ep in reversed(sorted(entrypoints.get_group_named(ep_group).items())): + + for name, ep in group: try: - paths.extend([*map(str, ep.load())]) + ep_start = time.time() + paths.extend(_get_paths_from_one_entry_point(ep)) except Exception: warnings.warn('Failed to load {} from entry_point "{}"\n{}'.format( ep_group, name, traceback.format_exc() )) + finally: + JUPYTER_ENTRY_POINT_TIMINGS and print( + f"{1e3 * (time.time() - ep_start):.2f}ms\t{ep_group}\t{name}" + ) + + + end = time.time() + JUPYTER_ENTRY_POINT_TIMINGS and print(f"{1e3 * (end - start):.2f}ms {ep_group}\tTOTAL") return paths + + def envset(name): """Return True if the given environment variable is set diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 1e9cd23..1a20446 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -5,15 +5,17 @@ import os import re +import sys import stat import shutil import tempfile -from unittest.mock import patch, Mock -import pytest -import sys +import importlib +import pytest import entrypoints +from unittest.mock import patch, Mock + from jupyter_core import paths from jupyter_core.paths import ( jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir, @@ -339,32 +341,52 @@ def test_secure_write_unix(): finally: shutil.rmtree(directory) +@pytest.fixture +def foo_entry_point_module(tmp_path): + mod = tmp_path / "foo/__init__.py" + mod.parent.mkdir() + mod.write_text("\n".join(["DATA = ['share']", "CONFIG = ['etc']"])) + + # importlib.util.find_spec(name) + + spec = Mock() + spec.origin = str(mod) + + with patch.object(importlib.util, 'find_spec', return_value=spec): + yield + @pytest.fixture -def data_path_entry_point(): +def data_path_entry_point(foo_entry_point_module): ep = Mock(spec=['load']) ep.name = JUPYTER_DATA_PATH_ENTRY_POINT - ep.load.return_value = ['/foo/share'] + ep.load.return_value = ['share'] + ep.module_name = "foo" + ep.object_name = "DATA" with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): yield ep -def test_data_entry_point(data_path_entry_point): - data_path = jupyter_path() - assert '/foo/share' in data_path - - @pytest.fixture -def config_path_entry_point(): +def config_path_entry_point(foo_entry_point_module): ep = Mock(spec=['load']) ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT - ep.load.return_value = ['/foo/etc'] + ep.load.return_value = ['etc'] + ep.module_name = "foo" + ep.object_name = "FOO" with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): yield ep -def test_config_entry_point(config_path_entry_point): +def test_data_entry_point(data_path_entry_point, tmp_path): + data_path = jupyter_path() + path = str(tmp_path / "foo/share") + assert path in data_path + + +def test_config_entry_point(config_path_entry_point, tmp_path): config_path = jupyter_config_path() - assert '/foo/etc' in config_path + path = str(tmp_path / "foo/etc") + assert path in config_path From 21d0d75641d8a7fdea744ca68bedf0d295fc7935 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 7 Mar 2021 12:17:10 -0500 Subject: [PATCH 38/50] fix flit test path --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d0a45c8..a2a0899 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,5 +137,5 @@ jobs: cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep jupyter_path_entrypoint_flit.share.jupyter - cat paths.txt | grep jupyter_path_entrypoint_flit.etc.jupyter + cat paths.txt | grep entrypoint_example_flit.share.jupyter + cat paths.txt | grep entrypoint_example_flit.etc.jupyter From e0ba42210edec5d4b4feb88594785b7855070542 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 7 Mar 2021 12:21:18 -0500 Subject: [PATCH 39/50] actually fix flit path --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2a0899..28b2013 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -137,5 +137,5 @@ jobs: cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep entrypoint_example_flit.share.jupyter - cat paths.txt | grep entrypoint_example_flit.etc.jupyter + cat paths.txt | grep entry_point_example_flit.share.jupyter + cat paths.txt | grep entry_point_example_flit.etc.jupyter From bd609f0a953b6eebf4d51248dad83e1aebe840b3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 07:53:07 -0500 Subject: [PATCH 40/50] add importlib_metadata entry_point finder --- .github/workflows/test.yml | 14 +++++++++++++- jupyter_core/paths.py | 29 +++++++++++++++++++++++------ setup.cfg | 2 ++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28b2013..23073d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,7 +103,19 @@ jobs: ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check - - name: Test with pytest + - name: Test with pytest (entrypoints) + run: | + set -eux + cd dist + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ + --cov=jupyter_core \ + --cov-report=term-missing:skip-covered \ + --cov-report=html \ + --no-cov-on-fail + + - name: Test with pytest (importlib_metadata) + env: + JUPYTER_ENTRY_POINT_FINDER: importlib_metadata run: | set -eux cd dist diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index bc1533c..8d59da2 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -20,6 +20,7 @@ import functools import traceback import importlib +import importlib_metadata from contextlib import contextmanager @@ -39,6 +40,7 @@ JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_paths" # TODO: remove, once the correct strategy is decided +JUPYTER_ENTRY_POINT_FINDER = os.environ.get("JUPYTER_ENTRY_POINT_FINDER") JUPYTER_ENTRY_POINT_STRATEGY = os.environ.get("JUPYTER_ENTRY_POINT_STRATEGY") JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") @@ -84,7 +86,12 @@ def _load_paths_from_one_entry_point(ep): """ get the paths from the entry_point target by importing """ loaded = ep.load() - spec = importlib.util.find_spec(ep.module_name) + if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": + module_name = ep.module + else: + module_name = ep.module_name + + spec = importlib.util.find_spec(module_name) module = importlib.util.module_from_spec(spec) origin = pathlib.Path(module.__file__).parent.resolve() return [str(origin / path) for path in loaded] @@ -93,8 +100,14 @@ def _load_paths_from_one_entry_point(ep): def _parse_paths_from_one_entry_point(ep): """ get the paths from the AST of the entry_point target without importing """ - static_mod = _load_static_module(ep.module_name) - raw_mod_paths = getattr(static_mod, ep.object_name, []) + if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": + module_name = ep.module + object_name = ep.attr + else: + module_name = ep.module_name + object_name = ep.object_name + static_mod = _load_static_module(module_name) + raw_mod_paths = getattr(static_mod, object_name, []) origin = pathlib.Path(static_mod.__file__).parent.resolve() return [str(origin / path) for path in raw_mod_paths] @@ -113,13 +126,17 @@ def _parse_or_load_paths_from_one_entry_point(ep): def _entry_point_paths(ep_group): start = time.time() - group = reversed(sorted(entrypoints.get_group_named(ep_group).items())) + if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": + group = [(ep.name, ep) for ep in importlib_metadata.entry_points(group=ep_group)] + else: + group = entrypoints.get_group_named(ep_group).items() + JUPYTER_ENTRY_POINT_TIMINGS and print( - f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded" + f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded with {JUPYTER_ENTRY_POINT_FINDER}" ) paths = [] - for name, ep in group: + for name, ep in reversed(sorted(group)): try: ep_start = time.time() paths.extend(_get_paths_from_one_entry_point(ep)) diff --git a/setup.cfg b/setup.cfg index a065370..8533853 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,8 @@ python_requires = >=3.6 install_requires = traitlets entrypoints + importlib_resources + importlib_metadata pywin32>=1.0 ; sys_platform == 'win32' [options.extras_require] From 5c006a34b00a7cc76427a1953ea0ba89c070aa8a Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 08:43:51 -0500 Subject: [PATCH 41/50] start making entry_point targets a singleton --- .../pyproject.toml | 8 +-- .../src/entry_point_example_flit/__init__.py | 4 +- .../setup.cfg | 8 +-- .../__init__.py | 4 +- jupyter_core/paths.py | 66 ++++++++++++------- jupyter_core/tests/test_paths.py | 4 +- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 0552304..24762cf 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -8,11 +8,11 @@ author = "Sir Robin" author-email = "robin@camelot.uk" home-page = "https://github.com/sirrobin/foobar" -[tool.flit.entrypoints.jupyter_config_paths] -entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATHS" +[tool.flit.entrypoints.jupyter_config_path] +entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATH" -[tool.flit.entrypoints.jupyter_data_paths] -entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATHS" +[tool.flit.entrypoints.jupyter_data_path] +entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATH" [tool.flit.sdist] include = ["src/entry_point_example_flit/etc/", "src/entry_point_example_flit/share/"] diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py index 86d014e..b780056 100644 --- a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py @@ -1,5 +1,5 @@ """an example of using the jupyter_*_paths entry_points with flit""" __version__ = "0.1.0" -JUPYTER_CONFIG_PATHS = ["etc/jupyter"] -JUPYTER_DATA_PATHS = ["share/jupyter"] +JUPYTER_CONFIG_PATH = "etc/jupyter" +JUPYTER_DATA_PATH = "share/jupyter" diff --git a/examples/jupyter_path_entrypoint_setuptools/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg index 71239d8..f2b08fb 100644 --- a/examples/jupyter_path_entrypoint_setuptools/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -33,7 +33,7 @@ where = src [options.entry_points] -jupyter_config_paths = - entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_CONFIG_PATHS -jupyter_data_paths = - entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATHS +jupyter_config_path = + entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_CONFIG_PATH +jupyter_data_path = + entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATH diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py index e0cb6a6..f834ffb 100644 --- a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -2,5 +2,5 @@ """ __version__ = "0.1.0" -JUPYTER_CONFIG_PATHS = ["etc/jupyter"] -JUPYTER_DATA_PATHS = ["share/jupyter"] +JUPYTER_CONFIG_PATH = "etc/jupyter" +JUPYTER_DATA_PATH = "share/jupyter" diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 8d59da2..9ccbbab 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -20,11 +20,17 @@ import functools import traceback import importlib -import importlib_metadata + from contextlib import contextmanager + +# TODO: only one of these will be kept import entrypoints +if sys.version_info >= (3, 8): + import importlib.metadata as importlib_metadata +else: + import importlib_metadata pjoin = os.path.join @@ -35,13 +41,14 @@ # the group names for entry_points in pyproject.toml, setup.py and .cfg to # provide discoverable paths -# the entrypoint target must be a list of strings to absolute paths -JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_paths" -JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_paths" +# the entry_point target MUST a single string of a relative POSIX path to the +# entry_point's importable +JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" +JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" # TODO: remove, once the correct strategy is decided -JUPYTER_ENTRY_POINT_FINDER = os.environ.get("JUPYTER_ENTRY_POINT_FINDER") -JUPYTER_ENTRY_POINT_STRATEGY = os.environ.get("JUPYTER_ENTRY_POINT_STRATEGY") +JUPYTER_ENTRY_POINT_FINDER = os.environ.get("JUPYTER_ENTRY_POINT_FINDER", "entrypoints") +JUPYTER_ENTRY_POINT_STRATEGY = os.environ.get("JUPYTER_ENTRY_POINT_STRATEGY", "PARSE_OR_LOAD") JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") @@ -82,54 +89,67 @@ def _load_static_module(module_name): return StaticModule(module_name) -def _load_paths_from_one_entry_point(ep): +def _load_path_from_one_entry_point(ep): """ get the paths from the entry_point target by importing """ - loaded = ep.load() + path = ep.load() if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": module_name = ep.module - else: + elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": module_name = ep.module_name + else: + raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) spec = importlib.util.find_spec(module_name) module = importlib.util.module_from_spec(spec) origin = pathlib.Path(module.__file__).parent.resolve() - return [str(origin / path) for path in loaded] + return str(origin / path) -def _parse_paths_from_one_entry_point(ep): +def _parse_path_from_one_entry_point(ep): """ get the paths from the AST of the entry_point target without importing """ if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": module_name = ep.module object_name = ep.attr - else: + elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": module_name = ep.module_name object_name = ep.object_name + else: + raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) static_mod = _load_static_module(module_name) - raw_mod_paths = getattr(static_mod, object_name, []) + path = getattr(static_mod, object_name) origin = pathlib.Path(static_mod.__file__).parent.resolve() - return [str(origin / path) for path in raw_mod_paths] + return str(origin / path) -def _parse_or_load_paths_from_one_entry_point(ep): - return _parse_paths_from_one_entry_point(ep) or _load_paths_from_one_entry_point(ep) +def _parse_or_load_path_from_one_entry_point(ep): + return _parse_path_from_one_entry_point(ep) or _load_path_from_one_entry_point(ep) +def _inspect_path_from_one_entry_point(ep): + raise NotImplementedError("TODO") -if JUPYTER_ENTRY_POINT_STRATEGY == "LOAD": - _get_paths_from_one_entry_point = _load_paths_from_one_entry_point +if JUPYTER_ENTRY_POINT_STRATEGY == "PARSE_OR_LOAD": + _get_path_from_one_entry_point = _parse_or_load_path_from_one_entry_point +elif JUPYTER_ENTRY_POINT_STRATEGY == "LOAD": + _get_path_from_one_entry_point = _load_path_from_one_entry_point elif JUPYTER_ENTRY_POINT_STRATEGY == "PARSE": - _get_paths_from_one_entry_point = _parse_paths_from_one_entry_point + _get_path_from_one_entry_point = _parse_path_from_one_entry_point +elif JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": + _get_path_from_one_entry_point == _inspect_path_from_one_entry_point else: - _get_paths_from_one_entry_point = _parse_or_load_paths_from_one_entry_point + raise NotImplementedError(JUPYTER_ENTRY_POINT_STRATEGY) + def _entry_point_paths(ep_group): start = time.time() if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - group = [(ep.name, ep) for ep in importlib_metadata.entry_points(group=ep_group)] - else: + group = [(ep.name, ep) for ep in importlib_metadata.entry_points()[ep_group]] + elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": group = entrypoints.get_group_named(ep_group).items() + else: + raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) JUPYTER_ENTRY_POINT_TIMINGS and print( f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded with {JUPYTER_ENTRY_POINT_FINDER}" @@ -139,7 +159,7 @@ def _entry_point_paths(ep_group): for name, ep in reversed(sorted(group)): try: ep_start = time.time() - paths.extend(_get_paths_from_one_entry_point(ep)) + paths.append(_get_path_from_one_entry_point(ep)) except Exception: warnings.warn('Failed to load {} from entry_point "{}"\n{}'.format( ep_group, diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 1a20446..05b1ed8 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -360,7 +360,7 @@ def foo_entry_point_module(tmp_path): def data_path_entry_point(foo_entry_point_module): ep = Mock(spec=['load']) ep.name = JUPYTER_DATA_PATH_ENTRY_POINT - ep.load.return_value = ['share'] + ep.load.return_value = 'share' ep.module_name = "foo" ep.object_name = "DATA" @@ -372,7 +372,7 @@ def data_path_entry_point(foo_entry_point_module): def config_path_entry_point(foo_entry_point_module): ep = Mock(spec=['load']) ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT - ep.load.return_value = ['etc'] + ep.load.return_value = 'etc' ep.module_name = "foo" ep.object_name = "FOO" From ba1f9eb3c6fb0cec8618bde49b6bb6e2c5d2fa85 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 09:21:05 -0500 Subject: [PATCH 42/50] rework tests --- jupyter_core/tests/test_entrypoints.py | 73 ++++++++++++++++++++++++++ jupyter_core/tests/test_paths.py | 56 +------------------- 2 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 jupyter_core/tests/test_entrypoints.py diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py new file mode 100644 index 0000000..c43adea --- /dev/null +++ b/jupyter_core/tests/test_entrypoints.py @@ -0,0 +1,73 @@ +"""TODO: these should be merged back into test_paths.py when they settle down +""" +from unittest.mock import patch, Mock +import importlib + +import pytest +import entrypoints + +from jupyter_core.paths import ( + jupyter_path, jupyter_config_path, JUPYTER_CONFIG_PATH_ENTRY_POINT, + JUPYTER_DATA_PATH_ENTRY_POINT +) + +@pytest.fixture +def foo_entry_point_module(tmp_path): + mod = tmp_path / "foo/__init__.py" + mod.parent.mkdir() + mod.write_text("\n".join(["DATA = 'share'", "CONFIG = 'etc'"])) + + spec = Mock() + spec.origin = str(mod) + + with patch.object(importlib.util, 'find_spec', return_value=spec): + yield + + +@pytest.fixture +def data_path_entry_point(foo_entry_point_module): + ep = Mock(spec=['load']) + ep.name = JUPYTER_DATA_PATH_ENTRY_POINT + ep.load.return_value = 'share' + ep.module_name = "foo" + ep.object_name = "DATA" + + with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): + yield ep + + +def test_data_entry_point(data_path_entry_point, tmp_path): + data_path = jupyter_path() + path = str(tmp_path / "foo/share") + assert path in data_path + + +@pytest.fixture +def bar_entry_point_module(tmp_path): + mod = tmp_path / "bar/__init__.py" + mod.parent.mkdir() + mod.write_text("\n".join(["DATA = 'share'", "CONFIG = 'etc'"])) + + spec = Mock() + spec.origin = str(mod) + + with patch.object(importlib.util, 'find_spec', return_value=spec): + yield + + +@pytest.fixture +def config_path_entry_point(bar_entry_point_module): + ep = Mock(spec=['load']) + ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT + ep.load.return_value = 'etc' + ep.module_name = "bar" + ep.object_name = "CONFIG" + + with patch.object(entrypoints, 'get_group_named', return_value={'bar': ep}): + yield ep + + +def test_config_entry_point(config_path_entry_point, tmp_path): + config_path = jupyter_config_path() + path = str(tmp_path / "bar/etc") + assert path in config_path diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 05b1ed8..0b2ed99 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -12,16 +12,14 @@ import importlib import pytest -import entrypoints -from unittest.mock import patch, Mock +from unittest.mock import patch from jupyter_core import paths from jupyter_core.paths import ( jupyter_config_dir, jupyter_data_dir, jupyter_runtime_dir, jupyter_path, jupyter_config_path, ENV_JUPYTER_PATH, - secure_write, is_hidden, is_file_hidden, JUPYTER_CONFIG_PATH_ENTRY_POINT, - JUPYTER_DATA_PATH_ENTRY_POINT + secure_write, is_hidden, is_file_hidden ) from .mocking import darwin, windows, linux @@ -340,53 +338,3 @@ def test_secure_write_unix(): assert f.read() == 'test 2' finally: shutil.rmtree(directory) - -@pytest.fixture -def foo_entry_point_module(tmp_path): - mod = tmp_path / "foo/__init__.py" - mod.parent.mkdir() - mod.write_text("\n".join(["DATA = ['share']", "CONFIG = ['etc']"])) - - # importlib.util.find_spec(name) - - spec = Mock() - spec.origin = str(mod) - - with patch.object(importlib.util, 'find_spec', return_value=spec): - yield - - -@pytest.fixture -def data_path_entry_point(foo_entry_point_module): - ep = Mock(spec=['load']) - ep.name = JUPYTER_DATA_PATH_ENTRY_POINT - ep.load.return_value = 'share' - ep.module_name = "foo" - ep.object_name = "DATA" - - with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): - yield ep - - -@pytest.fixture -def config_path_entry_point(foo_entry_point_module): - ep = Mock(spec=['load']) - ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT - ep.load.return_value = 'etc' - ep.module_name = "foo" - ep.object_name = "FOO" - - with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): - yield ep - - -def test_data_entry_point(data_path_entry_point, tmp_path): - data_path = jupyter_path() - path = str(tmp_path / "foo/share") - assert path in data_path - - -def test_config_entry_point(config_path_entry_point, tmp_path): - config_path = jupyter_config_path() - path = str(tmp_path / "foo/etc") - assert path in config_path From bcf84aa23435ff9fcea6b890adad4139459ae0ec Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 09:25:30 -0500 Subject: [PATCH 43/50] more cleanup of tests --- jupyter_core/tests/test_entrypoints.py | 4 ++++ jupyter_core/tests/test_paths.py | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py index c43adea..cf07854 100644 --- a/jupyter_core/tests/test_entrypoints.py +++ b/jupyter_core/tests/test_entrypoints.py @@ -1,5 +1,9 @@ """TODO: these should be merged back into test_paths.py when they settle down """ + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + from unittest.mock import patch, Mock import importlib diff --git a/jupyter_core/tests/test_paths.py b/jupyter_core/tests/test_paths.py index 0b2ed99..908f8a5 100644 --- a/jupyter_core/tests/test_paths.py +++ b/jupyter_core/tests/test_paths.py @@ -5,15 +5,12 @@ import os import re -import sys import stat import shutil import tempfile -import importlib - -import pytest - from unittest.mock import patch +import pytest +import sys from jupyter_core import paths from jupyter_core.paths import ( From 0b233f64654549b1d7daf97389202ca675707c4d Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 09:27:39 -0500 Subject: [PATCH 44/50] some more docs around test quirks --- jupyter_core/tests/test_entrypoints.py | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py index cf07854..b3f7ad1 100644 --- a/jupyter_core/tests/test_entrypoints.py +++ b/jupyter_core/tests/test_entrypoints.py @@ -15,6 +15,21 @@ JUPYTER_DATA_PATH_ENTRY_POINT ) + +def test_data_entry_point(data_path_entry_point, tmp_path): + data_path = jupyter_path() + path = str(tmp_path / "foo/share") + assert path in data_path + + +def test_config_entry_point(config_path_entry_point, tmp_path): + config_path = jupyter_config_path() + path = str(tmp_path / "bar/etc") + assert path in config_path + + +# there's a lot of duplication, as ugly path hackes get confused otheriwse + @pytest.fixture def foo_entry_point_module(tmp_path): mod = tmp_path / "foo/__init__.py" @@ -40,12 +55,6 @@ def data_path_entry_point(foo_entry_point_module): yield ep -def test_data_entry_point(data_path_entry_point, tmp_path): - data_path = jupyter_path() - path = str(tmp_path / "foo/share") - assert path in data_path - - @pytest.fixture def bar_entry_point_module(tmp_path): mod = tmp_path / "bar/__init__.py" @@ -69,9 +78,3 @@ def config_path_entry_point(bar_entry_point_module): with patch.object(entrypoints, 'get_group_named', return_value={'bar': ep}): yield ep - - -def test_config_entry_point(config_path_entry_point, tmp_path): - config_path = jupyter_config_path() - path = str(tmp_path / "bar/etc") - assert path in config_path From fbc118e43649c770d6ddc26e89773b000e2767aa Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 09:48:26 -0500 Subject: [PATCH 45/50] start importlib_resources --- .../pyproject.toml | 7 ++++ .../setup.cfg | 4 +++ jupyter_core/paths.py | 33 ++++++++++++++----- setup.cfg | 6 ++-- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 24762cf..9c60b2c 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -14,6 +14,13 @@ entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATH" [tool.flit.entrypoints.jupyter_data_path] entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATH" +[tool.flit.entrypoints.jupyter_config_resource] +entry-point-example-flit = "entry_point_example_flit:etc" + +[tool.flit.entrypoints.jupyter_data_resource] +entry-point-example-flit = "entry_point_example_flit:share" + + [tool.flit.sdist] include = ["src/entry_point_example_flit/etc/", "src/entry_point_example_flit/share/"] exclude = ["src/entry_point_example_flit/share/*/example_excluded_file_flit.json"] diff --git a/examples/jupyter_path_entrypoint_setuptools/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg index f2b08fb..32e71cb 100644 --- a/examples/jupyter_path_entrypoint_setuptools/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -37,3 +37,7 @@ jupyter_config_path = entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_CONFIG_PATH jupyter_data_path = entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATH +jupyter_config_resource = + entry-point-example-setuptools = entry_point_example_setuptools:etc +jupyter_data_path = + entry-point-example-setuptools = entry_point_example_setuptools:share diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 9ccbbab..6d8386b 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -25,13 +25,18 @@ from contextlib import contextmanager -# TODO: only one of these will be kept +# TODO: clean these up when the correct tools are chosen import entrypoints if sys.version_info >= (3, 8): import importlib.metadata as importlib_metadata else: import importlib_metadata +if sys.version_info >= (3, 9): + import importlib.resources as importlib_resource +else: + import importlib_resources + pjoin = os.path.join @@ -39,12 +44,6 @@ # It is used by BSD to indicate hidden files. UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) -# the group names for entry_points in pyproject.toml, setup.py and .cfg to -# provide discoverable paths -# the entry_point target MUST a single string of a relative POSIX path to the -# entry_point's importable -JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" -JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" # TODO: remove, once the correct strategy is decided JUPYTER_ENTRY_POINT_FINDER = os.environ.get("JUPYTER_ENTRY_POINT_FINDER", "entrypoints") @@ -52,6 +51,18 @@ JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") +# the group names for entry_points in pyproject.toml, setup.py and .cfg to +# provide discoverable paths +if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": + # the entry_point will be used directly (never imported) + JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_resource" + JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_resource" +else: + # the entry_point MUST resolve single string literal POSIX path relative to the + # entry_point's importable + JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" + JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" + # TODO: remove if not parsing # from https://github.com/pypa/setuptools/blob/23ee037d56a6d8ab957882e1a041f67924ae04da/setuptools/config.py#L19 class StaticModule: @@ -124,10 +135,14 @@ def _parse_path_from_one_entry_point(ep): def _parse_or_load_path_from_one_entry_point(ep): + """ first attempt static discovery of the entry_point target, fall back to import + """ return _parse_path_from_one_entry_point(ep) or _load_path_from_one_entry_point(ep) def _inspect_path_from_one_entry_point(ep): - raise NotImplementedError("TODO") + """ use the entrypoint metadata directly to discover the path + """ + raise NotImplementedError("woo") if JUPYTER_ENTRY_POINT_STRATEGY == "PARSE_OR_LOAD": _get_path_from_one_entry_point = _parse_or_load_path_from_one_entry_point @@ -136,7 +151,7 @@ def _inspect_path_from_one_entry_point(ep): elif JUPYTER_ENTRY_POINT_STRATEGY == "PARSE": _get_path_from_one_entry_point = _parse_path_from_one_entry_point elif JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": - _get_path_from_one_entry_point == _inspect_path_from_one_entry_point + _get_path_from_one_entry_point = _inspect_path_from_one_entry_point else: raise NotImplementedError(JUPYTER_ENTRY_POINT_STRATEGY) diff --git a/setup.cfg b/setup.cfg index 8533853..b8c0c90 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,10 +23,10 @@ packages = jupyter_core, jupyter_core.utils, jupyter_core.tests include_package_data = True python_requires = >=3.6 install_requires = - traitlets entrypoints - importlib_resources - importlib_metadata + importlib_metadata ; python_version < '3.8' + importlib_resources ; python_version < '3.9' + traitlets pywin32>=1.0 ; sys_platform == 'win32' [options.extras_require] From fd0d42db8ef08b293212bec81d379920e890db1d Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 10:24:43 -0500 Subject: [PATCH 46/50] adjust paths to make sorta worth with importlib_resources --- .../pyproject.toml | 2 +- .../entrypoint-example-flit.json | 0 .../example_excluded_file_flit.json | 0 .../{jupyter => }/example_file_flit.json | 0 .../MANIFEST.in | 2 +- .../setup.cfg | 2 +- .../jupyter_config.d/entrypoint-example.json | 0 .../example_excluded_file_setuptools.json | 0 .../example_file_setuptools.json | 0 jupyter_core/paths.py | 47 ++++++++++--------- 10 files changed, 29 insertions(+), 24 deletions(-) rename examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/{jupyter => }/jupyter_config.d/entrypoint-example-flit.json (100%) rename examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/{jupyter => }/example_excluded_file_flit.json (100%) rename examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/{jupyter => }/example_file_flit.json (100%) rename examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/{jupyter => }/jupyter_config.d/entrypoint-example.json (100%) rename examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/{jupyter => }/example_excluded_file_setuptools.json (100%) rename examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/{jupyter => }/example_file_setuptools.json (100%) diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 9c60b2c..92a9489 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -23,4 +23,4 @@ entry-point-example-flit = "entry_point_example_flit:share" [tool.flit.sdist] include = ["src/entry_point_example_flit/etc/", "src/entry_point_example_flit/share/"] -exclude = ["src/entry_point_example_flit/share/*/example_excluded_file_flit.json"] +exclude = ["src/entry_point_example_flit/share/example_excluded_file_flit.json"] diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter_config.d/entrypoint-example-flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter/jupyter_config.d/entrypoint-example-flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/etc/jupyter_config.d/entrypoint-example-flit.json diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_excluded_file_flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/example_excluded_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_excluded_file_flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/example_excluded_file_flit.json diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/example_file_flit.json similarity index 100% rename from examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/jupyter/example_file_flit.json rename to examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/share/example_file_flit.json diff --git a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in index f10e0f7..4e07645 100644 --- a/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in +++ b/examples/jupyter_path_entrypoint_setuptools/MANIFEST.in @@ -1,2 +1,2 @@ recursive-include src/entry_point_example_setuptools *.* -exclude src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json +exclude src/entry_point_example_setuptools/share/example_excluded_file_setuptools.json diff --git a/examples/jupyter_path_entrypoint_setuptools/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg index 32e71cb..51b2953 100644 --- a/examples/jupyter_path_entrypoint_setuptools/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -39,5 +39,5 @@ jupyter_data_path = entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATH jupyter_config_resource = entry-point-example-setuptools = entry_point_example_setuptools:etc -jupyter_data_path = +jupyter_data_resource = entry-point-example-setuptools = entry_point_example_setuptools:share diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter/jupyter_config.d/entrypoint-example.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter_config.d/entrypoint-example.json similarity index 100% rename from examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter/jupyter_config.d/entrypoint-example.json rename to examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/etc/jupyter_config.d/entrypoint-example.json diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/example_excluded_file_setuptools.json similarity index 100% rename from examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_excluded_file_setuptools.json rename to examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/example_excluded_file_setuptools.json diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/example_file_setuptools.json similarity index 100% rename from examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/jupyter/example_file_setuptools.json rename to examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/share/example_file_setuptools.json diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 6d8386b..2d6c75d 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -22,8 +22,7 @@ import importlib -from contextlib import contextmanager - +from contextlib import contextmanager, ExitStack # TODO: clean these up when the correct tools are chosen import entrypoints @@ -33,7 +32,7 @@ import importlib_metadata if sys.version_info >= (3, 9): - import importlib.resources as importlib_resource + import importlib.resources as importlib_resources else: import importlib_resources @@ -51,14 +50,16 @@ JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") -# the group names for entry_points in pyproject.toml, setup.py and .cfg to +# The group names for entry_points in pyproject.toml, setup.py and .cfg to # provide discoverable paths if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": - # the entry_point will be used directly (never imported) + # The entry_point MUST be the name of a directory, consisting of only + # letters, numbers, and underscores, adjacent to __init__.py + # and will never be loaded. JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_resource" JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_resource" else: - # the entry_point MUST resolve single string literal POSIX path relative to the + # The entry_point MUST resolve single string literal POSIX path relative to the # entry_point's importable JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" @@ -100,17 +101,21 @@ def _load_static_module(module_name): return StaticModule(module_name) -def _load_path_from_one_entry_point(ep): - """ get the paths from the entry_point target by importing - """ - path = ep.load() +def _get_ep_name_object(ep): if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - module_name = ep.module + return ep.module, ep.attr elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": - module_name = ep.module_name + return ep.module_name, ep.object_name else: raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) + +def _load_path_from_one_entry_point(ep): + """ get the paths from the entry_point target by importing + """ + path = ep.load() + module_name, object_name = _get_ep_name_object(ep) + spec = importlib.util.find_spec(module_name) module = importlib.util.module_from_spec(spec) origin = pathlib.Path(module.__file__).parent.resolve() @@ -120,14 +125,7 @@ def _load_path_from_one_entry_point(ep): def _parse_path_from_one_entry_point(ep): """ get the paths from the AST of the entry_point target without importing """ - if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - module_name = ep.module - object_name = ep.attr - elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": - module_name = ep.module_name - object_name = ep.object_name - else: - raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) + module_name, object_name = _get_ep_name_object(ep) static_mod = _load_static_module(module_name) path = getattr(static_mod, object_name) origin = pathlib.Path(static_mod.__file__).parent.resolve() @@ -142,7 +140,14 @@ def _parse_or_load_path_from_one_entry_point(ep): def _inspect_path_from_one_entry_point(ep): """ use the entrypoint metadata directly to discover the path """ - raise NotImplementedError("woo") + module_name, object_name = _get_ep_name_object(ep) + + try: + ExitStack().enter_context(importlib_resources.path(module_name, object_name)) + except IsADirectoryError as err: + return err.filename + + raise NotImplementedError("Must be a file") if JUPYTER_ENTRY_POINT_STRATEGY == "PARSE_OR_LOAD": _get_path_from_one_entry_point = _parse_or_load_path_from_one_entry_point From 9c486709bf074e23f295df85049dc761e87c0be2 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 11:44:14 -0500 Subject: [PATCH 47/50] add some strategy excursions to tests --- .github/workflows/test.yml | 44 ++++++++-- .../src/entry_point_example_flit/__init__.py | 8 +- .../__init__.py | 8 +- jupyter_core/paths.py | 8 +- jupyter_core/tests/test_entrypoints.py | 83 +++++++++++-------- 5 files changed, 104 insertions(+), 47 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 23073d5..50f1e82 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,7 +103,34 @@ jobs: ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check - - name: Test with pytest (entrypoints) + - name: Test with pytest (entrypoints, PARSE_OR_LOAD) + env: + JUPYTER_ENTRY_POINT_FINDER: entrypoints + run: | + set -eux + cd dist + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ + --cov=jupyter_core \ + --cov-report=term-missing:skip-covered \ + --cov-report=html \ + --no-cov-on-fail + + - name: Test with pytest (importlib_metadata, PARSE_OR_LOAD) + env: + JUPYTER_ENTRY_POINT_FINDER: importlib_metadata + run: | + set -eux + cd dist + ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ + --cov=jupyter_core \ + --cov-report=term-missing:skip-covered \ + --cov-report=html \ + --no-cov-on-fail + + - name: Test with pytest (entrypoints, INSPECT) + env: + JUPYTER_ENTRY_POINT_FINDER: entrypoints + JUPYTER_ENTRY_POINT_STRATEGY: INSPECT run: | set -eux cd dist @@ -113,9 +140,10 @@ jobs: --cov-report=html \ --no-cov-on-fail - - name: Test with pytest (importlib_metadata) + - name: Test with pytest (importlib_metadata, INSPECT) env: JUPYTER_ENTRY_POINT_FINDER: importlib_metadata + JUPYTER_ENTRY_POINT_STRATEGY: INSPECT run: | set -eux cd dist @@ -133,21 +161,21 @@ jobs: ${{ matrix.py-cmd }} -m pip install -vv codecov ${{ matrix.py-cmd }} -m codecov - - name: Test setuptools example + - name: Test setuptools example (develop) run: | set -eux cd examples/jupyter_path_entrypoint_setuptools ${{ matrix.py-cmd }} -m pip install -e . ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep entry_point_example_setuptools.share.jupyter - cat paths.txt | grep entry_point_example_setuptools.etc.jupyter + cat paths.txt | grep entry_point_example_setuptools.share + cat paths.txt | grep entry_point_example_setuptools.etc - - name: Test flit example + - name: Test flit example (develop) run: | set -eux ${{ matrix.py-cmd }} -m pip install flit cd examples/jupyter_path_entrypoint_flit ${{ matrix.py-cmd }} -m flit install --pth-file ${{ matrix.py-cmd }} -m jupyter --paths | tee paths.txt - cat paths.txt | grep entry_point_example_flit.share.jupyter - cat paths.txt | grep entry_point_example_flit.etc.jupyter + cat paths.txt | grep entry_point_example_flit.share + cat paths.txt | grep entry_point_example_flit.etc diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py index b780056..7a90ea4 100644 --- a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py @@ -1,5 +1,9 @@ """an example of using the jupyter_*_paths entry_points with flit""" +import time __version__ = "0.1.0" -JUPYTER_CONFIG_PATH = "etc/jupyter" -JUPYTER_DATA_PATH = "share/jupyter" +JUPYTER_CONFIG_PATH = "etc" +JUPYTER_DATA_PATH = "share" + +# this is added to simulate a slow-loading import +time.sleep(1) diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py index f834ffb..3dc738c 100644 --- a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -1,6 +1,10 @@ """an example of using the jupyter_*_paths entry_points in setuptools """ +import time __version__ = "0.1.0" -JUPYTER_CONFIG_PATH = "etc/jupyter" -JUPYTER_DATA_PATH = "share/jupyter" +JUPYTER_CONFIG_PATH = "etc" +JUPYTER_DATA_PATH = "share" + +# this is added to simulate a slow-loading import +time.sleep(1) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 2d6c75d..7a4e96a 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -22,7 +22,7 @@ import importlib -from contextlib import contextmanager, ExitStack +from contextlib import contextmanager # TODO: clean these up when the correct tools are chosen import entrypoints @@ -138,9 +138,13 @@ def _parse_or_load_path_from_one_entry_point(ep): return _parse_path_from_one_entry_point(ep) or _load_path_from_one_entry_point(ep) def _inspect_path_from_one_entry_point(ep): - """ use the entrypoint metadata directly to discover the path + """ use the entrypoint attribute name to discover the path without loading """ module_name, object_name = _get_ep_name_object(ep) + spec = importlib.util.find_spec(module_name) + module = importlib.util.module_from_spec(spec) + origin = pathlib.Path(module.__file__).parent.resolve() + return str(origin / object_name) try: ExitStack().enter_context(importlib_resources.path(module_name, object_name)) diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py index b3f7ad1..0450485 100644 --- a/jupyter_core/tests/test_entrypoints.py +++ b/jupyter_core/tests/test_entrypoints.py @@ -11,8 +11,14 @@ import entrypoints from jupyter_core.paths import ( - jupyter_path, jupyter_config_path, JUPYTER_CONFIG_PATH_ENTRY_POINT, - JUPYTER_DATA_PATH_ENTRY_POINT + jupyter_path, jupyter_config_path, + # these would stay + JUPYTER_CONFIG_PATH_ENTRY_POINT, JUPYTER_DATA_PATH_ENTRY_POINT, + # these might stay + importlib_metadata, importlib_resources, + # these would go + JUPYTER_ENTRY_POINT_FINDER, JUPYTER_ENTRY_POINT_STRATEGY, + JUPYTER_ENTRY_POINT_TIMINGS ) @@ -28,53 +34,64 @@ def test_config_entry_point(config_path_entry_point, tmp_path): assert path in config_path -# there's a lot of duplication, as ugly path hackes get confused otheriwse - +# there's a lot of duplication, as ugly path hacks get confused otheriwse @pytest.fixture def foo_entry_point_module(tmp_path): - mod = tmp_path / "foo/__init__.py" - mod.parent.mkdir() - mod.write_text("\n".join(["DATA = 'share'", "CONFIG = 'etc'"])) - - spec = Mock() - spec.origin = str(mod) - - with patch.object(importlib.util, 'find_spec', return_value=spec): + with _mock_modspec("foo", tmp_path): yield @pytest.fixture def data_path_entry_point(foo_entry_point_module): - ep = Mock(spec=['load']) - ep.name = JUPYTER_DATA_PATH_ENTRY_POINT - ep.load.return_value = 'share' - ep.module_name = "foo" - ep.object_name = "DATA" - - with patch.object(entrypoints, 'get_group_named', return_value={'foo': ep}): - yield ep + with _mock_entry_point(JUPYTER_DATA_PATH_ENTRY_POINT, "foo-config", "foo", "DATA", "share"): + yield @pytest.fixture def bar_entry_point_module(tmp_path): - mod = tmp_path / "bar/__init__.py" + with _mock_modspec("bar", tmp_path): + yield + + +@pytest.fixture +def config_path_entry_point(bar_entry_point_module): + loader = _mock_entry_point(JUPYTER_CONFIG_PATH_ENTRY_POINT, "bar-config", "bar", "CONFIG", "etc") + with loader: + yield + + +def _mock_modspec(name, tmp_path): + mod = tmp_path / f"{name}/__init__.py" mod.parent.mkdir() - mod.write_text("\n".join(["DATA = 'share'", "CONFIG = 'etc'"])) + + # if deriving the path from the entry_point spec, the module _can_ be empty + mod.write_text("\n".join( + [] + if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT" else + ["DATA = 'share'", "CONFIG = 'etc'"] + )) spec = Mock() spec.origin = str(mod) - with patch.object(importlib.util, 'find_spec', return_value=spec): - yield + return patch.object(importlib.util, 'find_spec', return_value=spec) -@pytest.fixture -def config_path_entry_point(bar_entry_point_module): +def _mock_entry_point(ep_group, ep_name, module_name, object_name, return_value): ep = Mock(spec=['load']) - ep.name = JUPYTER_CONFIG_PATH_ENTRY_POINT - ep.load.return_value = 'etc' - ep.module_name = "bar" - ep.object_name = "CONFIG" - - with patch.object(entrypoints, 'get_group_named', return_value={'bar': ep}): - yield ep + ep.name = ep_name + + if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": + object_name = return_value + + if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": + ep.module = module_name + ep.attr = return_value + return patch.object(importlib_metadata, 'entry_points', return_value={ep_group: [ep]}) + elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": + ep.load.return_value = return_value + ep.module_name = module_name + ep.object_name = object_name + return patch.object(entrypoints, 'get_group_named', return_value={ep_name: ep}) + else: + raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) From 06fa8706e69671f3dcc71cd0983667349f9f3883 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 11:48:54 -0500 Subject: [PATCH 48/50] handle case when no entry_points are installed --- jupyter_core/paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 7a4e96a..9c3ab0b 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -169,7 +169,7 @@ def _entry_point_paths(ep_group): start = time.time() if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - group = [(ep.name, ep) for ep in importlib_metadata.entry_points()[ep_group]] + group = [(ep.name, ep) for ep in importlib_metadata.entry_points().get(ep_group, [])] elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": group = entrypoints.get_group_named(ep_group).items() else: From 4d4cafd26fdb8e7546212bcbecf940821bf463f7 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 12:34:43 -0500 Subject: [PATCH 49/50] fix importlib_metadata finder --- jupyter_core/paths.py | 12 ------------ jupyter_core/tests/test_entrypoints.py | 7 ++++--- setup.cfg | 1 - 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 9c3ab0b..971a6fc 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -31,12 +31,6 @@ else: import importlib_metadata -if sys.version_info >= (3, 9): - import importlib.resources as importlib_resources -else: - import importlib_resources - - pjoin = os.path.join # UF_HIDDEN is a stat flag not defined in the stat module. @@ -146,12 +140,6 @@ def _inspect_path_from_one_entry_point(ep): origin = pathlib.Path(module.__file__).parent.resolve() return str(origin / object_name) - try: - ExitStack().enter_context(importlib_resources.path(module_name, object_name)) - except IsADirectoryError as err: - return err.filename - - raise NotImplementedError("Must be a file") if JUPYTER_ENTRY_POINT_STRATEGY == "PARSE_OR_LOAD": _get_path_from_one_entry_point = _parse_or_load_path_from_one_entry_point diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py index 0450485..75830c5 100644 --- a/jupyter_core/tests/test_entrypoints.py +++ b/jupyter_core/tests/test_entrypoints.py @@ -14,8 +14,8 @@ jupyter_path, jupyter_config_path, # these would stay JUPYTER_CONFIG_PATH_ENTRY_POINT, JUPYTER_DATA_PATH_ENTRY_POINT, - # these might stay - importlib_metadata, importlib_resources, + # these might have to stay + importlib_metadata, # these would go JUPYTER_ENTRY_POINT_FINDER, JUPYTER_ENTRY_POINT_STRATEGY, JUPYTER_ENTRY_POINT_TIMINGS @@ -85,8 +85,9 @@ def _mock_entry_point(ep_group, ep_name, module_name, object_name, return_value) object_name = return_value if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": + ep.load.return_value = return_value ep.module = module_name - ep.attr = return_value + ep.attr = object_name return patch.object(importlib_metadata, 'entry_points', return_value={ep_group: [ep]}) elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": ep.load.return_value = return_value diff --git a/setup.cfg b/setup.cfg index b8c0c90..d26ce2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,6 @@ python_requires = >=3.6 install_requires = entrypoints importlib_metadata ; python_version < '3.8' - importlib_resources ; python_version < '3.9' traitlets pywin32>=1.0 ; sys_platform == 'win32' From 6fc165173c44c107d2a2d4977c26dd2762e8d0e8 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 9 Mar 2021 15:21:26 -0500 Subject: [PATCH 50/50] roll back to just entrypoints, single-path-per-entry-point --- .github/workflows/test.yml | 42 +----- docs/changelog.rst | 2 +- .../pyproject.toml | 9 +- .../src/entry_point_example_flit/__init__.py | 12 +- .../setup.cfg | 6 +- .../__init__.py | 9 +- jupyter_core/paths.py | 131 ++---------------- jupyter_core/tests/test_entrypoints.py | 48 ++----- setup.cfg | 1 - 9 files changed, 32 insertions(+), 228 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50f1e82..b3a31fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,47 +103,7 @@ jobs: ${{ matrix.py-cmd }} -m pip freeze ${{ matrix.py-cmd }} -m pip check - - name: Test with pytest (entrypoints, PARSE_OR_LOAD) - env: - JUPYTER_ENTRY_POINT_FINDER: entrypoints - run: | - set -eux - cd dist - ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ - --cov=jupyter_core \ - --cov-report=term-missing:skip-covered \ - --cov-report=html \ - --no-cov-on-fail - - - name: Test with pytest (importlib_metadata, PARSE_OR_LOAD) - env: - JUPYTER_ENTRY_POINT_FINDER: importlib_metadata - run: | - set -eux - cd dist - ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ - --cov=jupyter_core \ - --cov-report=term-missing:skip-covered \ - --cov-report=html \ - --no-cov-on-fail - - - name: Test with pytest (entrypoints, INSPECT) - env: - JUPYTER_ENTRY_POINT_FINDER: entrypoints - JUPYTER_ENTRY_POINT_STRATEGY: INSPECT - run: | - set -eux - cd dist - ${{ matrix.py-cmd }} -m pytest -vv --ff --pyargs jupyter_core \ - --cov=jupyter_core \ - --cov-report=term-missing:skip-covered \ - --cov-report=html \ - --no-cov-on-fail - - - name: Test with pytest (importlib_metadata, INSPECT) - env: - JUPYTER_ENTRY_POINT_FINDER: importlib_metadata - JUPYTER_ENTRY_POINT_STRATEGY: INSPECT + - name: Test with pytest run: | set -eux cd dist diff --git a/docs/changelog.rst b/docs/changelog.rst index ffea690..6f7ada6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,7 +10,7 @@ Changes in jupyter-core `on GitHub `__ -- Add new ``jupyter_data_paths`` and ``jupyter_config_paths`` ``entry_points`` +- Add new ``jupyter_data_path`` and ``jupyter_config_path`` ``entry_points`` (:ghpull:`209`) to allow python packages to extend the data and config paths in ``jupyter --paths``. These paths are considered immediately after those put in-place with ``data_files``, but work with modern python packaging tools. diff --git a/examples/jupyter_path_entrypoint_flit/pyproject.toml b/examples/jupyter_path_entrypoint_flit/pyproject.toml index 92a9489..37cee43 100644 --- a/examples/jupyter_path_entrypoint_flit/pyproject.toml +++ b/examples/jupyter_path_entrypoint_flit/pyproject.toml @@ -9,18 +9,11 @@ author-email = "robin@camelot.uk" home-page = "https://github.com/sirrobin/foobar" [tool.flit.entrypoints.jupyter_config_path] -entry-point-example-flit = "entry_point_example_flit:JUPYTER_CONFIG_PATH" - -[tool.flit.entrypoints.jupyter_data_path] -entry-point-example-flit = "entry_point_example_flit:JUPYTER_DATA_PATH" - -[tool.flit.entrypoints.jupyter_config_resource] entry-point-example-flit = "entry_point_example_flit:etc" -[tool.flit.entrypoints.jupyter_data_resource] +[tool.flit.entrypoints.jupyter_data_path] entry-point-example-flit = "entry_point_example_flit:share" - [tool.flit.sdist] include = ["src/entry_point_example_flit/etc/", "src/entry_point_example_flit/share/"] exclude = ["src/entry_point_example_flit/share/example_excluded_file_flit.json"] diff --git a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py index 7a90ea4..c10ede5 100644 --- a/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py +++ b/examples/jupyter_path_entrypoint_flit/src/entry_point_example_flit/__init__.py @@ -1,9 +1,5 @@ -"""an example of using the jupyter_*_paths entry_points with flit""" -import time -__version__ = "0.1.0" - -JUPYTER_CONFIG_PATH = "etc" -JUPYTER_DATA_PATH = "share" +"""an example of using the jupyter_*_paths entry_points with flit -# this is added to simulate a slow-loading import -time.sleep(1) +The entry_points are defined in pyproject.toml under tool.flit.entrypoints +""" +__version__ = "0.1.0" diff --git a/examples/jupyter_path_entrypoint_setuptools/setup.cfg b/examples/jupyter_path_entrypoint_setuptools/setup.cfg index 51b2953..5c4670b 100644 --- a/examples/jupyter_path_entrypoint_setuptools/setup.cfg +++ b/examples/jupyter_path_entrypoint_setuptools/setup.cfg @@ -34,10 +34,6 @@ where = [options.entry_points] jupyter_config_path = - entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_CONFIG_PATH -jupyter_data_path = - entry-point-example-setuptools = entry_point_example_setuptools:JUPYTER_DATA_PATH -jupyter_config_resource = entry-point-example-setuptools = entry_point_example_setuptools:etc -jupyter_data_resource = +jupyter_data_path = entry-point-example-setuptools = entry_point_example_setuptools:share diff --git a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py index 3dc738c..5e4f1d1 100644 --- a/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py +++ b/examples/jupyter_path_entrypoint_setuptools/src/entry_point_example_setuptools/__init__.py @@ -1,10 +1,5 @@ """an example of using the jupyter_*_paths entry_points in setuptools + +The entrypoints are defined in setup.cfg under [options.entry_points] """ -import time __version__ = "0.1.0" - -JUPYTER_CONFIG_PATH = "etc" -JUPYTER_DATA_PATH = "share" - -# this is added to simulate a slow-loading import -time.sleep(1) diff --git a/jupyter_core/paths.py b/jupyter_core/paths.py index 971a6fc..4a6e679 100644 --- a/jupyter_core/paths.py +++ b/jupyter_core/paths.py @@ -21,15 +21,9 @@ import traceback import importlib - from contextlib import contextmanager -# TODO: clean these up when the correct tools are chosen import entrypoints -if sys.version_info >= (3, 8): - import importlib.metadata as importlib_metadata -else: - import importlib_metadata pjoin = os.path.join @@ -38,133 +32,31 @@ UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) -# TODO: remove, once the correct strategy is decided -JUPYTER_ENTRY_POINT_FINDER = os.environ.get("JUPYTER_ENTRY_POINT_FINDER", "entrypoints") -JUPYTER_ENTRY_POINT_STRATEGY = os.environ.get("JUPYTER_ENTRY_POINT_STRATEGY", "PARSE_OR_LOAD") -JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") - - -# The group names for entry_points in pyproject.toml, setup.py and .cfg to -# provide discoverable paths -if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": - # The entry_point MUST be the name of a directory, consisting of only - # letters, numbers, and underscores, adjacent to __init__.py - # and will never be loaded. - JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_resource" - JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_resource" -else: - # The entry_point MUST resolve single string literal POSIX path relative to the - # entry_point's importable - JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" - JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" - -# TODO: remove if not parsing -# from https://github.com/pypa/setuptools/blob/23ee037d56a6d8ab957882e1a041f67924ae04da/setuptools/config.py#L19 -class StaticModule: - """ - Attempt to load the module by the name - """ - def __init__(self, name): - spec = importlib.util.find_spec(name) - - with open(spec.origin) as strm: - src = strm.read() - module = ast.parse(src) - vars(self).update(locals()) - del self.self - # add a path - self.__file__ = spec.origin - - def __getattr__(self, attr): - try: - return next( - ast.literal_eval(statement.value) - for statement in self.module.body - if isinstance(statement, ast.Assign) - for target in statement.targets - if isinstance(target, ast.Name) and target.id == attr - ) - except Exception as e: - raise AttributeError( - "{self.name} has no attribute {attr}".format(**locals()) - ) from e - -# TODO: remove if not parsing -@functools.lru_cache(maxsize=1024) -def _load_static_module(module_name): - return StaticModule(module_name) - - -def _get_ep_name_object(ep): - if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - return ep.module, ep.attr - elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": - return ep.module_name, ep.object_name - else: - raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) - - -def _load_path_from_one_entry_point(ep): - """ get the paths from the entry_point target by importing - """ - path = ep.load() - module_name, object_name = _get_ep_name_object(ep) +# The entry_point MUST be the name of a directory, consisting of only +# letters, numbers, and underscores, adjacent to __init__.py +JUPYTER_DATA_PATH_ENTRY_POINT = "jupyter_data_path" +JUPYTER_CONFIG_PATH_ENTRY_POINT = "jupyter_config_path" - spec = importlib.util.find_spec(module_name) - module = importlib.util.module_from_spec(spec) - origin = pathlib.Path(module.__file__).parent.resolve() - return str(origin / path) - - -def _parse_path_from_one_entry_point(ep): - """ get the paths from the AST of the entry_point target without importing - """ - module_name, object_name = _get_ep_name_object(ep) - static_mod = _load_static_module(module_name) - path = getattr(static_mod, object_name) - origin = pathlib.Path(static_mod.__file__).parent.resolve() - return str(origin / path) +# TODO: decide whether to keep, or grow a logging endpoint +JUPYTER_ENTRY_POINT_TIMINGS = os.environ.get("JUPYTER_ENTRY_POINT_TIMINGS") -def _parse_or_load_path_from_one_entry_point(ep): - """ first attempt static discovery of the entry_point target, fall back to import - """ - return _parse_path_from_one_entry_point(ep) or _load_path_from_one_entry_point(ep) - -def _inspect_path_from_one_entry_point(ep): +def _get_path_from_one_entry_point(ep): """ use the entrypoint attribute name to discover the path without loading """ - module_name, object_name = _get_ep_name_object(ep) - spec = importlib.util.find_spec(module_name) + spec = importlib.util.find_spec(ep.module_name) module = importlib.util.module_from_spec(spec) origin = pathlib.Path(module.__file__).parent.resolve() - return str(origin / object_name) - - -if JUPYTER_ENTRY_POINT_STRATEGY == "PARSE_OR_LOAD": - _get_path_from_one_entry_point = _parse_or_load_path_from_one_entry_point -elif JUPYTER_ENTRY_POINT_STRATEGY == "LOAD": - _get_path_from_one_entry_point = _load_path_from_one_entry_point -elif JUPYTER_ENTRY_POINT_STRATEGY == "PARSE": - _get_path_from_one_entry_point = _parse_path_from_one_entry_point -elif JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": - _get_path_from_one_entry_point = _inspect_path_from_one_entry_point -else: - raise NotImplementedError(JUPYTER_ENTRY_POINT_STRATEGY) + return str(origin / ep.object_name) def _entry_point_paths(ep_group): start = time.time() - if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - group = [(ep.name, ep) for ep in importlib_metadata.entry_points().get(ep_group, [])] - elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": - group = entrypoints.get_group_named(ep_group).items() - else: - raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) + group = entrypoints.get_group_named(ep_group).items() JUPYTER_ENTRY_POINT_TIMINGS and print( - f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded with {JUPYTER_ENTRY_POINT_FINDER}" + f"{1e3 * (time.time() - start):.2f}ms {ep_group} loaded" ) paths = [] @@ -189,7 +81,6 @@ def _entry_point_paths(ep_group): return paths - def envset(name): """Return True if the given environment variable is set diff --git a/jupyter_core/tests/test_entrypoints.py b/jupyter_core/tests/test_entrypoints.py index 75830c5..c37e8f3 100644 --- a/jupyter_core/tests/test_entrypoints.py +++ b/jupyter_core/tests/test_entrypoints.py @@ -4,21 +4,16 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from unittest.mock import patch, Mock import importlib +from unittest.mock import patch, Mock -import pytest import entrypoints +import pytest + from jupyter_core.paths import ( jupyter_path, jupyter_config_path, - # these would stay JUPYTER_CONFIG_PATH_ENTRY_POINT, JUPYTER_DATA_PATH_ENTRY_POINT, - # these might have to stay - importlib_metadata, - # these would go - JUPYTER_ENTRY_POINT_FINDER, JUPYTER_ENTRY_POINT_STRATEGY, - JUPYTER_ENTRY_POINT_TIMINGS ) @@ -43,7 +38,7 @@ def foo_entry_point_module(tmp_path): @pytest.fixture def data_path_entry_point(foo_entry_point_module): - with _mock_entry_point(JUPYTER_DATA_PATH_ENTRY_POINT, "foo-config", "foo", "DATA", "share"): + with _mock_entry_point(JUPYTER_DATA_PATH_ENTRY_POINT, "foo-config", "foo", "share"): yield @@ -55,7 +50,7 @@ def bar_entry_point_module(tmp_path): @pytest.fixture def config_path_entry_point(bar_entry_point_module): - loader = _mock_entry_point(JUPYTER_CONFIG_PATH_ENTRY_POINT, "bar-config", "bar", "CONFIG", "etc") + loader = _mock_entry_point(JUPYTER_CONFIG_PATH_ENTRY_POINT, "bar-config", "bar", "etc") with loader: yield @@ -63,36 +58,15 @@ def config_path_entry_point(bar_entry_point_module): def _mock_modspec(name, tmp_path): mod = tmp_path / f"{name}/__init__.py" mod.parent.mkdir() - - # if deriving the path from the entry_point spec, the module _can_ be empty - mod.write_text("\n".join( - [] - if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT" else - ["DATA = 'share'", "CONFIG = 'etc'"] - )) - + mod.write_text('__version__ = "0.1.0"') spec = Mock() spec.origin = str(mod) - return patch.object(importlib.util, 'find_spec', return_value=spec) -def _mock_entry_point(ep_group, ep_name, module_name, object_name, return_value): - ep = Mock(spec=['load']) +def _mock_entry_point(ep_group, ep_name, module_name, object_name): + ep = Mock() ep.name = ep_name - - if JUPYTER_ENTRY_POINT_STRATEGY == "INSPECT": - object_name = return_value - - if JUPYTER_ENTRY_POINT_FINDER == "importlib_metadata": - ep.load.return_value = return_value - ep.module = module_name - ep.attr = object_name - return patch.object(importlib_metadata, 'entry_points', return_value={ep_group: [ep]}) - elif JUPYTER_ENTRY_POINT_FINDER == "entrypoints": - ep.load.return_value = return_value - ep.module_name = module_name - ep.object_name = object_name - return patch.object(entrypoints, 'get_group_named', return_value={ep_name: ep}) - else: - raise NotImplementedError(JUPYTER_ENTRY_POINT_FINDER) + ep.module_name = module_name + ep.object_name = object_name + return patch.object(entrypoints, 'get_group_named', return_value={ep_name: ep}) diff --git a/setup.cfg b/setup.cfg index d26ce2f..ea9765c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,6 @@ include_package_data = True python_requires = >=3.6 install_requires = entrypoints - importlib_metadata ; python_version < '3.8' traitlets pywin32>=1.0 ; sys_platform == 'win32'