notebook_shim-0.2.3/CHANGELOG.md0000644000000000000000000001150413615410400013106 0ustar00# Changelog ## 0.2.3 ([Full Changelog](https://github.com/jupyter/notebook_shim/compare/v0.2.2...bb84e6475a757b6299cb07ec3ce027a8c57d052e)) ### Maintenance and upkeep improvements - Add show_banner to ignored_traits [#29](https://github.com/jupyter/notebook_shim/pull/29) ([@echarles](https://github.com/echarles)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyter/notebook_shim/graphs/contributors?from=2022-11-03&to=2023-04-24&type=c)) [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3Aecharles+updated%3A2022-11-03..2023-04-24&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3AZsailer+updated%3A2022-11-03..2023-04-24&type=Issues) ## 0.2.2 ([Full Changelog](https://github.com/jupyter/notebook_shim/compare/v0.2.0...e1f1219888d9bfa69fc0e1f130f6f1bcf3825fcb)) ### Bugs fixed - Fixes #7 [#24](https://github.com/jupyter/notebook_shim/pull/24) ([@dleen](https://github.com/dleen)) - Add config file [#23](https://github.com/jupyter/notebook_shim/pull/23) ([@dleen](https://github.com/dleen)) ### Maintenance and upkeep improvements - add releaser v2 workflows [#25](https://github.com/jupyter/notebook_shim/pull/25) ([@Zsailer](https://github.com/Zsailer)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyter/notebook_shim/graphs/contributors?from=2022-10-17&to=2022-11-03&type=c)) [@dleen](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3Adleen+updated%3A2022-10-17..2022-11-03&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3AZsailer+updated%3A2022-10-17..2022-11-03&type=Issues) ## 0.2.1 ([Full Changelog](https://github.com/jupyter/notebook_shim/compare/v0.2.0...b72af7411fa41115c3025a95f261e10bf0221fb0)) ### Bugs fixed - Add config file [#23](https://github.com/jupyter/notebook_shim/pull/23) ([@dleen](https://github.com/dleen)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyter/notebook_shim/graphs/contributors?from=2022-10-17&to=2022-11-02&type=c)) [@dleen](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3Adleen+updated%3A2022-10-17..2022-11-02&type=Issues) ## 0.2.0 ([Full Changelog](https://github.com/jupyter/notebook_shim/compare/v0.1.0...4e4228d7bb2d2e04cc204db3de7f2f567d65c38a)) ### Enhancements made - raise ceiling on jupyter_server dependency to < 3 [#12](https://github.com/jupyter/notebook_shim/pull/12) ([@Zsailer](https://github.com/Zsailer)) ### Maintenance and upkeep improvements - remove dev version [#15](https://github.com/jupyter/notebook_shim/pull/15) ([@Zsailer](https://github.com/Zsailer)) - switch to hatch build backend [#14](https://github.com/jupyter/notebook_shim/pull/14) ([@Zsailer](https://github.com/Zsailer)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyter/notebook_shim/graphs/contributors?from=2022-02-10&to=2022-10-17&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3Ablink1073+updated%3A2022-02-10..2022-10-17&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3Ajtpio+updated%3A2022-02-10..2022-10-17&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook_shim+involves%3AZsailer+updated%3A2022-02-10..2022-10-17&type=Issues) ## 0.1.0 ([Full Changelog](https://github.com/jupyterlab/notebook_shim/compare/first-commit...5b433fa298f741c7d71c9a3e7e85f17b2207300f)) ### Enhancements made - [WIP] Move over initial shim logic [#1](https://github.com/jupyterlab/notebook_shim/pull/1) ([@Zsailer](https://github.com/Zsailer)) ### Bugs fixed - Port missing logic and get unit tests working [#5](https://github.com/jupyterlab/notebook_shim/pull/5) ([@Zsailer](https://github.com/Zsailer)) ### Maintenance and upkeep improvements - Add a test workflow on CI [#4](https://github.com/jupyterlab/notebook_shim/pull/4) ([@jtpio](https://github.com/jtpio)) - Add `check-release` workflow on CI [#3](https://github.com/jupyterlab/notebook_shim/pull/3) ([@jtpio](https://github.com/jtpio)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/notebook_shim/graphs/contributors?from=2022-01-19&to=2022-02-10&type=c)) [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Fnotebook_shim+involves%3Ajtpio+updated%3A2022-01-19..2022-02-10&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fnotebook_shim+involves%3Awelcome+updated%3A2022-01-19..2022-02-10&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fnotebook_shim+involves%3AZsailer+updated%3A2022-01-19..2022-02-10&type=Issues) notebook_shim-0.2.3/MANIFEST.in0000644000000000000000000000017113615410400013031 0ustar00include LICENSE # added by check-manifest include *.md recursive-include docs *.md recursive-include notebook_shim *.py notebook_shim-0.2.3/RELEASE.md0000644000000000000000000000032513615410400012676 0ustar00# Making a Release of `notebook_shim` ## Using `jupyter_releaser` The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). notebook_shim-0.2.3/conftest.py0000644000000000000000000000006213615410400013471 0ustar00pytest_plugins = ["jupyter_server.pytest_plugin"] notebook_shim-0.2.3/.github/workflows/check-release.yml0000644000000000000000000000125213615410400020107 0ustar00name: Check Release on: push: branches: - '*' pull_request: branches: - '*' permissions: contents: write jobs: check_release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Check Release uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Upload Distributions uses: actions/upload-artifact@v2 with: name: jupyter-resource-usage-releaser-dist-${{ github.run_number }} path: .jupyter_releaser_checkout/dist notebook_shim-0.2.3/.github/workflows/enforce-label.yml0000644000000000000000000000042413615410400020112 0ustar00name: Enforce PR label on: pull_request: types: [labeled, unlabeled, opened, edited, synchronize] jobs: enforce-label: runs-on: ubuntu-latest steps: - name: enforce-triage-label uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 notebook_shim-0.2.3/.github/workflows/prep-release.yaml0000644000000000000000000000265713615410400020153 0ustar00name: "Step 1: Prep Release" on: workflow_dispatch: inputs: version_spec: description: "New Version Specifier" default: "next" required: false branch: description: "The branch to target" required: false post_version_spec: description: "Post Version Specifier" required: false since: description: "Use PRs with activity since this date or git reference" required: false since_last_stable: description: "Use PRs with activity since the last stable git tag" required: false type: boolean jobs: prep_release: runs-on: ubuntu-latest steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Prep Release id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} post_version_spec: ${{ github.event.inputs.post_version_spec }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} since: ${{ github.event.inputs.since }} since_last_stable: ${{ github.event.inputs.since_last_stable }} - name: "** Next Step **" run: | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" notebook_shim-0.2.3/.github/workflows/publish-release.yaml0000644000000000000000000000347513615410400020652 0ustar00name: "Step 2: Publish Release" on: workflow_dispatch: inputs: branch: description: "The target branch" required: false release_url: description: "The URL of the draft GitHub release" required: false steps_to_skip: description: "Comma separated list of steps to skip" required: false jobs: publish_release: runs-on: ubuntu-latest steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} steps_to_skip: ${{ github.event.inputs.steps_to_skip }} - name: Finalize Release id: finalize-release env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} TWINE_USERNAME: __token__ NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} release_url: ${{ steps.populate-release.outputs.release_url }} - name: "** Next Step **" if: ${{ success() }} run: | echo "Verify the final release" echo ${{ steps.finalize-release.outputs.release_url }} - name: "** Failure Message **" if: ${{ failure() }} run: | echo "Failed to Publish the Draft Release Url:" echo ${{ steps.populate-release.outputs.release_url }} notebook_shim-0.2.3/.github/workflows/test.yml0000644000000000000000000000141413615410400016373 0ustar00name: Test on: push: branches: - main pull_request: branches: "*" jobs: build: runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] python-version: ["3.7", "3.10"] steps: - name: Checkout uses: actions/checkout@v2 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Install pip dependencies run: | pip install -v -e ".[test]" pytest-cov - name: Check pip environment run: | pip freeze pip check - name: Test with pytest run: | python -m pytest -vv --cov=notebook_shim notebook_shim --cov-report term-missing:skip-covered notebook_shim-0.2.3/jupyter_server_config.d/notebook_shim.json0000644000000000000000000000015213615410400021664 0ustar00{ "ServerApp": { "jpserver_extensions": { "notebook_shim": true } } } notebook_shim-0.2.3/notebook_shim/__init__.py0000644000000000000000000000017613615410400016251 0ustar00def _jupyter_server_extension_paths(): return [ { 'module': 'notebook_shim.nbserver', } ] notebook_shim-0.2.3/notebook_shim/_version.py0000644000000000000000000000006713615410400016335 0ustar00version_info = (0, 2, 3, "", "") __version__ = "0.2.3" notebook_shim-0.2.3/notebook_shim/nbserver.py0000644000000000000000000001210513615410400016333 0ustar00""" This module contains a Jupyter Server extension that attempts to make classic server and notebook extensions work in the new server. Unfortunately, you'll notice that requires some major monkey-patching. The goal is that this extension will only be used as a temporary patch to transition extension authors from classic notebook server to jupyter_server. """ import os import types import inspect from functools import wraps from jupyter_core.paths import jupyter_config_path from traitlets.traitlets import is_trait from jupyter_server.services.config.manager import ConfigManager from .traits import NotebookAppTraits class ClassProxyError(Exception): pass def proxy(obj1, obj2, name, overwrite=False): """Redirects a method, property, or trait from object 1 to object 2.""" if hasattr(obj1, name) and overwrite is False: raise ClassProxyError( "Cannot proxy the attribute '{name}' from {cls2} because " "{cls1} already has this attribute.".format( name=name, cls1=obj1.__class__, cls2=obj2.__class__ ) ) attr = getattr(obj2, name) # First check if this thing is a trait (see traitlets) cls_attr = getattr(obj2.__class__, name) if is_trait(cls_attr) or type(attr) == property: thing = property(lambda self: getattr(obj2, name)) elif isinstance(attr, types.MethodType): @wraps(attr) def thing(self, *args, **kwargs): return attr(*args, **kwargs) # Anything else appended on the class is just an attribute of the class. else: thing = attr setattr(obj1.__class__, name, thing) def public_members(obj): members = inspect.getmembers(obj) return [m for m, _ in members if not m.startswith('_')] def diff_members(obj1, obj2): """Return all attribute names found in obj2 but not obj1""" m1 = public_members(obj1) m2 = public_members(obj2) return set(m2).difference(m1) def get_nbserver_extensions(config_dirs): cm = ConfigManager(read_config_path=config_dirs) section = cm.get("jupyter_notebook_config") extensions = section.get('NotebookApp', {}).get('nbserver_extensions', {}) return extensions def _link_jupyter_server_extension(serverapp): # Get the extension manager from the server manager = serverapp.extension_manager logger = serverapp.log # Hack that patches the enabled extensions list, prioritizing # jupyter nbclassic. In the future, it would be much better # to incorporate a dependency injection system in the # Extension manager that allows extensions to list # their dependency tree and sort that way. def sorted_extensions(self): """Dictionary with extension package names as keys and an ExtensionPackage objects as values. """ # Sort the keys and keys = sorted(self.extensions.keys()) keys.remove("notebook_shim") keys = ["notebook_shim"] + keys return {key: self.extensions[key] for key in keys} manager.__class__.sorted_extensions = property(sorted_extensions) # Look to see if nbclassic is enabled. if so, # link the nbclassic extension here to load # its config. Then, port its config to the serverapp # for backwards compatibility. try: pkg = manager.extensions["notebook_shim"] pkg.link_point("notebook_shim", serverapp) point = pkg.extension_points["notebook_shim"] nbapp = point.app except Exception: nbapp = NotebookAppTraits() # Proxy NotebookApp traits through serverapp to notebookapp. members = diff_members(serverapp, nbapp) for m in members: proxy(serverapp, nbapp, m) # Find jupyter server extensions listed as notebook server extensions. jupyter_paths = jupyter_config_path() config_dirs = jupyter_paths + [serverapp.config_dir] nbserver_extensions = get_nbserver_extensions(config_dirs) # Link all extensions found in the old locations for # notebook server extensions. for name, enabled in nbserver_extensions.items(): # If the extension is already enabled in the manager, i.e. # because it was discovered already by Jupyter Server # through its jupyter_server_config, then don't re-enable here. if name not in manager.extensions: successful = manager.add_extension(name, enabled=enabled) if successful: logger.info( "{name} | extension was found and enabled by notebook_shim. " "Consider moving the extension to Jupyter Server's " "extension paths.".format(name=name) ) manager.link_extension(name) def _load_jupyter_server_extension(serverapp): # Patch the config service manager to find the # proper path for old notebook frontend extensions config_manager = serverapp.config_manager read_config_path = config_manager.read_config_path read_config_path += [os.path.join(p, 'nbconfig') for p in jupyter_config_path()] config_manager.read_config_path = read_config_path notebook_shim-0.2.3/notebook_shim/shim.py0000644000000000000000000002672513615410400015462 0ustar00from functools import wraps from copy import deepcopy from traitlets import TraitError from traitlets.config.loader import ( Config, ) from jupyter_core.application import JupyterApp from jupyter_server.serverapp import ServerApp from jupyter_server.extension.application import ExtensionApp from .traits import NotebookAppTraits def NBAPP_AND_SVAPP_SHIM_MSG(trait_name): return ( "'{trait_name}' was found in both NotebookApp " "and ServerApp. This is likely a recent change. " "This config will only be set in NotebookApp. " "Please check if you should also config these traits in " "ServerApp for your purpose.".format( trait_name=trait_name, ) ) def NBAPP_TO_SVAPP_SHIM_MSG(trait_name): return ( "'{trait_name}' has moved from NotebookApp to " "ServerApp. This config will be passed to ServerApp. " "Be sure to update your config before " "our next release.".format( trait_name=trait_name, ) ) def EXTAPP_AND_NBAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' is found in {extapp_name}, NotebookApp, " "and ServerApp. This is a recent change. " "This config will only be set in {extapp_name}. " "Please check if you should also config these traits in " "NotebookApp and ServerApp for your purpose.".format( trait_name=trait_name, extapp_name=extapp_name ) ) def EXTAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' is found in both {extapp_name} " "and ServerApp. This is a recent change. " "This config will only be set in {extapp_name}. " "Please check if you should also config these traits in " "ServerApp for your purpose.".format( trait_name=trait_name, extapp_name=extapp_name ) ) def EXTAPP_AND_NBAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' is found in both {extapp_name} " "and NotebookApp. This is a recent change. " "This config will only be set in {extapp_name}. " "Please check if you should also config these traits in " "NotebookApp for your purpose.".format( trait_name=trait_name, extapp_name=extapp_name ) ) def NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' is not found in {extapp_name}, but " "it was found in both NotebookApp " "and ServerApp. This is likely a recent change. " "This config will only be set in ServerApp. " "Please check if you should also config these traits in " "NotebookApp for your purpose.".format( trait_name=trait_name, extapp_name=extapp_name ) ) def EXTAPP_TO_SVAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' has moved from {extapp_name} to " "ServerApp. Be sure to update your config before " "our next release.".format( trait_name=trait_name, extapp_name=extapp_name ) ) def EXTAPP_TO_NBAPP_SHIM_MSG(trait_name, extapp_name): return ( "'{trait_name}' has moved from {extapp_name} to " "NotebookApp. Be sure to update your config before " "our next release.".format( trait_name=trait_name, extapp_name=extapp_name ) ) # A tuple of traits that shouldn't be shimmed or throw any # warnings of any kind. IGNORED_TRAITS = ("open_browser", "log_level", "log_format", "default_url", "show_banner") class NotebookConfigShimMixin: """A Mixin class for shimming configuration from NotebookApp to ServerApp. This class handles warnings, errors, etc. This class should be used during a transition period for apps that are switching from depending on NotebookApp to ServerApp. After one release cycle, this class can be safely removed from the inheriting class. TL;DR The entry point to shimming is at the `update_config` method. Once traits are loaded, before updating config across all configurable objects, this class injects a method to reroute traits to their *most logical* classes. This class raises warnings when: 1. a trait has moved. 2. a trait is redundant across classes. Redundant traits across multiple classes now must be configured separately, *or* removed from their old location to avoid this warning. For a longer description on how individual traits are handled, read the docstring under `shim_config_from_notebook_to_jupyter_server`. """ @wraps(JupyterApp.update_config) def update_config(self, config): # Shim traits to handle transition from NotebookApp to ServerApp shimmed_config = self.shim_config_from_notebook_to_jupyter_server( config) super().update_config(shimmed_config) def shim_config_from_notebook_to_jupyter_server(self, config): """Reorganizes a config object to reroute traits to their expected destinations after the transition from NotebookApp to ServerApp. A detailed explanation of how traits are handled: 1. If the argument is prefixed with `ServerApp`, pass this trait to `ServerApp`. 2. If the argument is prefixed with `NotebookApp`, * If the argument is a trait of `NotebookApp` *and* `ServerApp`: 1. Raise a warning—**for the extension developers**—that there's redundant traits. 2. Pass trait to `NotebookApp`. * If the argument is a trait of just `ServerApp` only (i.e. the trait moved from `NotebookApp` to `ServerApp`): 1. Raise a "this trait has moved" **for the user**. 3. Pass trait to `ServerApp`. * If the argument is a trait of `NotebookApp` only, pass trait to `NotebookApp`. * If the argument is not found in any object, raise a `"Trait not found."` error. 3. If the argument is prefixed with `ExtensionApp`: * If the argument is a trait of `ExtensionApp`, `NotebookApp`, and `ServerApp`, 1. Raise a warning about redundancy. 2. Pass to the ExtensionApp * If the argument is a trait of `ExtensionApp` and `NotebookApp`, 1. Raise a warning about redundancy. 2. Pass to ExtensionApp. * If the argument is a trait of `ExtensionApp` and `ServerApp`, 1. Raise a warning about redundancy. 2. Pass to ExtensionApp. * If the argument is a trait of `ExtensionApp`. 1. Pass to ExtensionApp. * If the argument is a trait of `NotebookApp` but not `ExtensionApp`, 1. Raise a warning that trait has likely moved to NotebookApp. 2. Pass to NotebookApp * If the arguent is a trait of `ServerApp` but not `ExtensionApp`, 1. Raise a warning that the trait has likely moved to ServerApp. 2. Pass to ServerApp. * else * Raise a TraitError: "trait not found." """ extapp_name = self.__class__.__name__ # Pop out the various configurable objects that we need to evaluate. nbapp_config = config.pop('NotebookApp', {}) svapp_config = config.pop('ServerApp', {}) extapp_config = config.pop(extapp_name, {}) # Created shimmed configs. # Leave the rest of the config alone. config_shim = deepcopy(config) svapp_config_shim = {} nbapp_config_shim = {} extapp_config_shim = {} extapp_traits = ( self.__class__.class_trait_names() + ExtensionApp.class_trait_names() ) svapp_traits = ServerApp.class_trait_names() nbapp_traits = ( NotebookAppTraits.class_trait_names() + ExtensionApp.class_trait_names() ) # 1. Handle ServerApp traits. svapp_config_shim.update(svapp_config) # 2. Handle NotebookApp traits. warning_msg = None for trait_name, trait_value in nbapp_config.items(): in_svapp = trait_name in svapp_traits in_nbapp = trait_name in nbapp_traits if trait_name in IGNORED_TRAITS: # Pass trait through without any warning message. nbapp_config_shim.update({trait_name: trait_value}) elif in_svapp and in_nbapp: warning_msg = NBAPP_AND_SVAPP_SHIM_MSG(trait_name) nbapp_config_shim.update({trait_name: trait_value}) elif in_svapp: warning_msg = NBAPP_TO_SVAPP_SHIM_MSG(trait_name) svapp_config_shim.update({trait_name: trait_value}) elif in_nbapp: nbapp_config_shim.update({trait_name: trait_value}) else: raise TraitError("Trait, {}, not found.".format(trait_name)) # Raise a warning if it's given. if warning_msg: self.log.warning(warning_msg) # 3. Handle ExtensionApp traits. warning_msg = None for trait_name, trait_value in extapp_config.items(): in_extapp = trait_name in extapp_traits in_svapp = trait_name in svapp_traits in_nbapp = trait_name in nbapp_traits if trait_name in IGNORED_TRAITS: # Pass trait through without any warning message. extapp_config_shim.update({trait_name: trait_value}) elif all([in_extapp, in_svapp, in_nbapp]): warning_msg = EXTAPP_AND_NBAPP_AND_SVAPP_SHIM_MSG( trait_name, extapp_name ) extapp_config_shim.update({trait_name: trait_value}) elif in_extapp and in_svapp: warning_msg = EXTAPP_AND_SVAPP_SHIM_MSG( trait_name, extapp_name ) extapp_config_shim.update({trait_name: trait_value}) elif in_extapp and in_nbapp: warning_msg = EXTAPP_AND_NBAPP_SHIM_MSG( trait_name, extapp_name ) extapp_config_shim.update({trait_name: trait_value}) elif in_extapp: extapp_config_shim.update({trait_name: trait_value}) elif in_svapp and in_nbapp: warning_msg = NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG( trait_name, extapp_name ) svapp_config_shim.update({trait_name: trait_value}) elif in_svapp: warning_msg = EXTAPP_TO_SVAPP_SHIM_MSG( trait_name, extapp_name ) svapp_config_shim.update({trait_name: trait_value}) elif in_nbapp: warning_msg = EXTAPP_TO_NBAPP_SHIM_MSG( trait_name, extapp_name ) nbapp_config_shim.update({trait_name: trait_value}) else: raise TraitError("Trait, {}, not found.".format(trait_name)) # Raise warning if one is given if warning_msg: self.log.warning(warning_msg) # Build config for shimmed traits. new_config = Config({ 'NotebookApp': nbapp_config_shim, 'ServerApp': svapp_config_shim, }) if extapp_config_shim: new_config.update(Config({ self.__class__.__name__: extapp_config_shim })) # Update the full config with new values config_shim.update(new_config) return config_shim notebook_shim-0.2.3/notebook_shim/traits.py0000644000000000000000000001274013615410400016020 0ustar00import os from traitlets import ( HasTraits, Dict, Unicode, List, Bool, observe, default ) from jupyter_core.paths import jupyter_path from jupyter_server.transutils import _i18n from jupyter_server.utils import url_path_join class NotebookAppTraits(HasTraits): ignore_minified_js = Bool(False, config=True, help=_i18n( 'Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation'), ) jinja_environment_options = Dict(config=True, help=_i18n("Supply extra arguments that will be passed to Jinja environment.")) jinja_template_vars = Dict( config=True, help=_i18n( "Extra variables to supply to jinja templates when rendering."), ) enable_mathjax = Bool(True, config=True, help="""Whether to enable MathJax for typesetting math/TeX MathJax is the javascript library Jupyter uses to render math/LaTeX. It is very large, so you may want to disable it if you have a slow internet connection, or for offline use of the notebook. When disabled, equations etc. will appear as their untransformed TeX source. """ ) @observe('enable_mathjax') def _update_enable_mathjax(self, change): """set mathjax url to empty if mathjax is disabled""" if not change['new']: self.mathjax_url = u'' extra_static_paths = List(Unicode(), config=True, help="""Extra paths to search for serving static files. This allows adding javascript/css to be available from the notebook server machine, or overriding individual files in the IPython""" ) @property def static_file_path(self): """return extra paths + the default location""" return self.extra_static_paths static_custom_path = List(Unicode(), help=_i18n( """Path to search for custom.js, css""") ) @default('static_custom_path') def _default_static_custom_path(self): return [ os.path.join(self.config_dir, 'custom') ] extra_template_paths = List(Unicode(), config=True, help=_i18n("""Extra paths to search for serving jinja templates. Can be used to override templates from notebook.templates.""") ) @property def template_file_path(self): """return extra paths + the default locations""" return self.extra_template_paths extra_nbextensions_path = List(Unicode(), config=True, help=_i18n( """extra paths to look for Javascript notebook extensions""") ) @property def nbextensions_path(self): """The path to look for Javascript notebook extensions""" path = self.extra_nbextensions_path + jupyter_path('nbextensions') # FIXME: remove IPython nbextensions path after a migration period try: from IPython.paths import get_ipython_dir except ImportError: pass else: path.append(os.path.join(get_ipython_dir(), 'nbextensions')) return path mathjax_url = Unicode("", config=True, help="""A custom url for MathJax.js. Should be in the form of a case-sensitive url to MathJax, for example: /static/components/MathJax/MathJax.js """ ) @property def static_url_prefix(self): """Get the static url prefix for serving static files.""" return super(NotebookAppTraits, self).static_url_prefix @default('mathjax_url') def _default_mathjax_url(self): if not self.enable_mathjax: return u'' static_url_prefix = self.static_url_prefix return url_path_join(static_url_prefix, 'components', 'MathJax', 'MathJax.js') @observe('mathjax_url') def _update_mathjax_url(self, change): new = change['new'] if new and not self.enable_mathjax: # enable_mathjax=False overrides mathjax_url self.mathjax_url = u'' else: self.log.info(_i18n("Using MathJax: %s"), new) mathjax_config = Unicode("TeX-AMS-MML_HTMLorMML-full,Safe", config=True, help=_i18n( """The MathJax.js configuration file that is to be used.""") ) @observe('mathjax_config') def _update_mathjax_config(self, change): self.log.info( _i18n("Using MathJax configuration file: %s"), change['new']) quit_button = Bool(True, config=True, help="""If True, display a button in the dashboard to quit (shutdown the notebook server).""" ) nbserver_extensions = Dict({}, config=True, help=(_i18n("Dict of Python modules to load as notebook server extensions." "Entry values can be used to enable and disable the loading of" "the extensions. The extensions will be loaded in alphabetical " "order.")) ) notebook_shim-0.2.3/notebook_shim/tests/__init__.py0000644000000000000000000000000013615410400017375 0ustar00notebook_shim-0.2.3/notebook_shim/tests/mockextension.py0000644000000000000000000000152213615410400020536 0ustar00 from traitlets import ( Unicode, Bool, ) from jupyter_server.extension.application import ExtensionApp from notebook_shim import shim def _jupyter_server_extension_points(): return [ { "module": "notebook_shim.tests.mockextension", "app": MockExtensionApp } ] class MockExtensionApp( shim.NotebookConfigShimMixin, ExtensionApp ): """Mock an extension app that previously inherited NotebookApp.""" name = 'mockextension' # ------ Traits found ServerApp, NotebookApp, and MockExtensionApp default_url = Unicode(config=True) # ------ Traits found Notebook and MockExtensionApp enable_mathjax = Bool(config=True) # ------ Traits found ServerApp and MockExtensionApp allow_origin = Unicode(config=True) allow_origin_pat = Unicode(config=True) notebook_shim-0.2.3/notebook_shim/tests/test_extension.py0000644000000000000000000000741213615410400020727 0ustar00 import io import logging import pytest from traitlets import default from .mockextension import MockExtensionApp from notebook_shim import shim @pytest.fixture def read_app_logs(capsys): """Fixture that returns a callable to read the current output from the application's logs that was printed to sys.stderr. """ def _inner(): captured = capsys.readouterr() return captured.err return _inner @pytest.fixture def jp_server_config(capsys): return { "ServerApp": { "jpserver_extensions": { "notebook_shim": True, "notebook_shim.tests.mockextension": True } } } @pytest.fixture def extensionapp(jp_serverapp): return jp_serverapp.extension_manager.extension_points["mockextension"].app def list_test_params(param_input): """""" params = [] for test in param_input: name, value = test[0], test[1] option = ( '--MockExtensionApp.' '{name}={value}' .format(name=name, value=value) ) params.append([[option], name, value]) return params @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('enable_mathjax', False) ]) ) def test_EXTAPP_AND_NBAPP_SHIM_MSG( read_app_logs, extensionapp, jp_argv, trait_name, trait_value ): log = read_app_logs() # Verify a shim warning appeared. log_msg = shim.EXTAPP_AND_NBAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log # Verify the trait was updated. assert getattr(extensionapp, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('allow_origin', ''), ('allow_origin_pat', ''), ]) ) def test_EXTAPP_AND_SVAPP_SHIM_MSG( read_app_logs, extensionapp, jp_argv, trait_name, trait_value ): log = read_app_logs() # Verify a shim warning appeared. log_msg = shim.EXTAPP_AND_SVAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log # Verify the trait was updated. assert getattr(extensionapp, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('jinja_environment_options', {}), ('jinja_template_vars', {}), ('extra_template_paths', []), ('quit_button', True), ]) ) def test_NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG( read_app_logs, extensionapp, jp_argv, trait_name, trait_value ): log = read_app_logs() # Verify a shim warning appeared. log_msg = shim.NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log # Verify the trait was updated. assert getattr(extensionapp.serverapp, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('allow_credentials', False), ]) ) def test_EXTAPP_TO_SVAPP_SHIM_MSG( read_app_logs, extensionapp, jp_argv, trait_name, trait_value ): log = read_app_logs() # Verify a shim warning appeared. log_msg = shim.EXTAPP_TO_SVAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log # Verify the trait was updated. assert getattr(extensionapp.serverapp, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('mathjax_config', 'TEST'), ('mathjax_url', 'TEST') ]) ) def test_EXTAPP_TO_NBAPP_SHIM_MSG( read_app_logs, extensionapp, jp_argv, trait_name, trait_value ): log = read_app_logs() # Verify a shim warning appeared. log_msg = shim.EXTAPP_TO_NBAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log notebook_shim-0.2.3/notebook_shim/tests/confs/jupyter_my_ext_config.py0000644000000000000000000000003713615410400023374 0ustar00c.MyExt.hello = 'My extension' notebook_shim-0.2.3/notebook_shim/tests/confs/jupyter_notebook_config.py0000644000000000000000000000015113615410400023704 0ustar00c.NotebookApp.allow_credentials = False c.NotebookApp.port = 8889 c.NotebookApp.password_required = True notebook_shim-0.2.3/notebook_shim/tests/confs/jupyter_server_config.py0000644000000000000000000000003013615410400023366 0ustar00c.ServerApp.port = 7774 notebook_shim-0.2.3/.gitignore0000644000000000000000000000340713615410400013270 0ustar00# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ notebook_shim-0.2.3/LICENSE0000644000000000000000000000277713615410400012316 0ustar00BSD 3-Clause License Copyright (c) 2022 Project Jupyter Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the copyright holder 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 HOLDER 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. notebook_shim-0.2.3/README.md0000644000000000000000000000217213615410400012555 0ustar00# Notebook Shim This project provides a way for JupyterLab and other frontends to switch to [Jupyter Server](https://github.com/jupyter/jupyter_server/) for their Python Web application backend. ## Basic Usage Install from PyPI: ``` pip install notebook_shim ``` This will automatically enable the extension in Jupyter Server. ## Usage This project also includes an API for shimming traits that moved from `NotebookApp` in to `ServerApp` in Jupyter Server. This can be used by applications that subclassed `NotebookApp` to leverage the Python server backend of Jupyter Notebooks. Such extensions should *now* switch to `ExtensionApp` API in Jupyter Server and add `NotebookConfigShimMixin` in their inheritance list to properly handle moved traits. For example, an application class that previously looked like: ```python from notebook.notebookapp import NotebookApp class MyApplication(NotebookApp): ``` should switch to look something like: ```python from jupyter_server.extension.application import ExtensionApp from notebook_shim.shim import NotebookConfigShimMixin class MyApplication(NotebookConfigShimMixin, ExtensionApp): ```notebook_shim-0.2.3/pyproject.toml0000644000000000000000000000412613615410400014213 0ustar00[build-system] requires = ["hatchling >=1.0"] build-backend = "hatchling.build" [project] name = "notebook_shim" dynamic = [ "version", ] readme = "README.md" license = { file = "LICENSE" } description = "A shim layer for notebook traits and config" authors = [{name = "Jupyter Development Team", email = "jupyter@googlegroups.com"}] keywords = ["ipython", "jupyter"] classifiers = [ "Framework :: Jupyter", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", ] requires-python = ">=3.7" dependencies = [ "jupyter_server>=1.8,<3" ] [tool.hatch.build.targets.wheel.shared-data] "jupyter_server_config.d/notebook_shim.json" = "etc/jupyter/jupyter_server_config.d/notebook_shim.json" [project.optional-dependencies] test = [ "pytest", "pytest-tornasync", "pytest-console-scripts", "pytest_jupyter" ] [tool.hatch.version] path = "notebook_shim/_version.py" [tool.tbump.version] current = "0.2.3" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? ''' [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" [[tool.tbump.file]] src = "notebook_shim/_version.py" version_template = '({major}, {minor}, {patch}, "{channel}", "{release}")' [[tool.tbump.file]] src = "notebook_shim/_version.py" version_template = "{major}.{minor}.{patch}{channel}{release}" [[tool.tbump.field]] name = "channel" default = "" [[tool.tbump.field]] name = "release" default = "" [tool.check-manifest] ignore = ["tbump.toml", ".*", "conftest.py"] [tool.pytest.ini_options] norecursedirs = ["confs"] testpaths = [ "notebook_shim/tests" ] [tool.jupyter-releaser] skip = ["check-links"] notebook_shim-0.2.3/PKG-INFO0000644000000000000000000000770013615410400012375 0ustar00Metadata-Version: 2.1 Name: notebook_shim Version: 0.2.3 Summary: A shim layer for notebook traits and config Author-email: Jupyter Development Team License: BSD 3-Clause License Copyright (c) 2022 Project Jupyter Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the copyright holder 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 HOLDER 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. License-File: LICENSE Keywords: ipython,jupyter Classifier: Framework :: Jupyter Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Requires-Python: >=3.7 Requires-Dist: jupyter-server<3,>=1.8 Provides-Extra: test Requires-Dist: pytest; extra == 'test' Requires-Dist: pytest-console-scripts; extra == 'test' Requires-Dist: pytest-jupyter; extra == 'test' Requires-Dist: pytest-tornasync; extra == 'test' Description-Content-Type: text/markdown # Notebook Shim This project provides a way for JupyterLab and other frontends to switch to [Jupyter Server](https://github.com/jupyter/jupyter_server/) for their Python Web application backend. ## Basic Usage Install from PyPI: ``` pip install notebook_shim ``` This will automatically enable the extension in Jupyter Server. ## Usage This project also includes an API for shimming traits that moved from `NotebookApp` in to `ServerApp` in Jupyter Server. This can be used by applications that subclassed `NotebookApp` to leverage the Python server backend of Jupyter Notebooks. Such extensions should *now* switch to `ExtensionApp` API in Jupyter Server and add `NotebookConfigShimMixin` in their inheritance list to properly handle moved traits. For example, an application class that previously looked like: ```python from notebook.notebookapp import NotebookApp class MyApplication(NotebookApp): ``` should switch to look something like: ```python from jupyter_server.extension.application import ExtensionApp from notebook_shim.shim import NotebookConfigShimMixin class MyApplication(NotebookConfigShimMixin, ExtensionApp): ```