pax_global_header00006660000000000000000000000064141210600260014502gustar00rootroot0000000000000052 comment=0d6fd44c590f777d8d3488ae4a8d37559e080b08 nbclassic-0.3.2/000077500000000000000000000000001412106002600134455ustar00rootroot00000000000000nbclassic-0.3.2/.github/000077500000000000000000000000001412106002600150055ustar00rootroot00000000000000nbclassic-0.3.2/.github/workflows/000077500000000000000000000000001412106002600170425ustar00rootroot00000000000000nbclassic-0.3.2/.github/workflows/pythonpackage.yml000066400000000000000000000032701412106002600224240ustar00rootroot00000000000000name: Testing nbclassic on: push: branches: - master pull_request: branches: '*' jobs: build: runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] python-version: ['3.6', '3.7', '3.8', '3.9', 'pypy3'] exclude: - os: windows python-version: pypy3 steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Upgrade pip, etc. run: | python -m pip install --user --upgrade pip setuptools wheel - name: Get pip cache dir id: pip-cache run: | echo "::set-output name=dir::$(pip cache dir)" - name: Cache pip uses: actions/cache@v1 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- - name: Install pip dependencies run: | pip install -v -e ".[test]" pytest-cov - name: Check pip environment run: | pip freeze pip check # - name: Install Jupyter Server from source # run: | # cd .. # git clone https://github.com/jupyter/jupyter_server.git # cd jupyter_server # pip install -e . # cd ../nbclassic - name: Run the help command run: | jupyter nbclassic -h - name: Test with pytest run: | pytest -vv --cov nbclassic --cov-report term-missing:skip-covered nbclassic-0.3.2/.gitignore000066400000000000000000000033051412106002600154360ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class .vscode/ # 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 # celery beat schedule file celerybeat-schedule # 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/ nbclassic-0.3.2/CHANGELOG.md000066400000000000000000000225431412106002600152640ustar00rootroot00000000000000# Changelog ## 0.3.2 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/0.3.1...32ff24b059573c51e1bf91c426f8fd2fe6dac665)) ### Merged PRs - ExtensionManager's link_extension changed call signature [#64](https://github.com/jupyterlab/nbclassic/pull/64) ([@athornton](https://github.com/athornton)) - Fix link to the Jupyter Notebook repo in README [#62](https://github.com/jupyterlab/nbclassic/pull/62) ([@saiwing-yeung](https://github.com/saiwing-yeung)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2021-05-21&to=2021-09-17&type=c)) [@athornton](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aathornton+updated%3A2021-05-21..2021-09-17&type=Issues) | [@saiwing-yeung](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Asaiwing-yeung+updated%3A2021-05-21..2021-09-17&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Awelcome+updated%3A2021-05-21..2021-09-17&type=Issues) ## 0.3.1 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/0.3.0...f1b8540eb6e7ee33c3b923454366e34adbcaad1a)) ### Maintenance and upkeep improvements - bump jupyter_server dependency to 1.8 [#58](https://github.com/jupyterlab/nbclassic/pull/58) ([@Zsailer](https://github.com/Zsailer)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2021-05-20&to=2021-05-21&type=c)) [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3AZsailer+updated%3A2021-05-20..2021-05-21&type=Issues) ## 0.3.0 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/0.2.8...0df2d3341205609c1a1b4e2c8fc6e8959c7e828a)) ### Enhancements made - Support creating terminal with a given name. [#52](https://github.com/jupyterlab/nbclassic/pull/52) ([@cailiang9](https://github.com/cailiang9)) ### Bugs fixed - BUG fix: correct redirection to {base_url}/edit/*. [#55](https://github.com/jupyterlab/nbclassic/pull/55) ([@cailiang9](https://github.com/cailiang9)) ### Maintenance and upkeep improvements - patch server's sorted_extensions to prioritize nbclassic [#56](https://github.com/jupyterlab/nbclassic/pull/56) ([@Zsailer](https://github.com/Zsailer)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2021-05-11&to=2021-05-20&type=c)) [@cailiang9](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Acailiang9+updated%3A2021-05-11..2021-05-20&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Awelcome+updated%3A2021-05-11..2021-05-20&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3AZsailer+updated%3A2021-05-11..2021-05-20&type=Issues) ## 0.2.8 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/0.2.7...eabc8408210a8b4e76efec2c57b5b4f9778b1c2a)) ### Merged PRs - Remove forced sorting of extensions [#49](https://github.com/jupyterlab/nbclassic/pull/49) ([@minrk](https://github.com/minrk)) - Add Changelog [#48](https://github.com/jupyterlab/nbclassic/pull/48) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2021-04-08&to=2021-05-11&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ablink1073+updated%3A2021-04-08..2021-05-11&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aminrk+updated%3A2021-04-08..2021-05-11&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Awelcome+updated%3A2021-04-08..2021-05-11&type=Issues) ## 0.2.7 ## Merged PRs * Fix deprecation warning when importing jupyter_server.transutils._ [#47](https://github.com/jupyterlab/nbclassic/pull/47) ([@martinRenou](https://github.com/martinRenou)) * Add a redirect handler to open non-notebook files from the cli [#45](https://github.com/jupyterlab/nbclassic/pull/45) ([@jtpio](https://github.com/jtpio)) * Add default_url trait to NotebookApp [#42](https://github.com/jupyterlab/nbclassic/pull/42) ([@afshin](https://github.com/afshin)) * Fix GitHub Actions badge [#40](https://github.com/jupyterlab/nbclassic/pull/40) ([@jtpio](https://github.com/jtpio)) * Run jupyter nbclassic -h on CI [#29](https://github.com/jupyterlab/nbclassic/pull/29) ([@jtpio](https://github.com/jtpio)) ## Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2021-01-08&to=2021-04-08&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aafshin+updated%3A2021-01-08..2021-04-08&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ablink1073+updated%3A2021-01-08..2021-04-08&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ajtpio+updated%3A2021-01-08..2021-04-08&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3AmartinRenou+updated%3A2021-01-08..2021-04-08&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Awelcome+updated%3A2021-01-08..2021-04-08&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3AZsailer+updated%3A2021-01-08..2021-04-08&type=Issues) ## 0.2.6 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/47ff8cb...917c9f7)) ### Merged PRs * Ignore some traits in shim layer [#38](https://github.com/jupyterlab/nbclassic/pull/38) ([@afshin](https://github.com/afshin)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-12-15&to=2021-01-08&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aafshin+updated%3A2020-12-15..2021-01-08&type=Issues) ## 0.2.5 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/cf3790c...47ff8cb)) ### Merged PRs * Update jupyter_server version, update to use prefixed fixtures [#37](https://github.com/jupyterlab/nbclassic/pull/37) ([@afshin](https://github.com/afshin)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-12-08&to=2020-12-15&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aafshin+updated%3A2020-12-08..2020-12-15&type=Issues) ## 0.2.4 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/46bb6d5...cf3790c)) ### Merged PRs * Exclude tests from dist [#36](https://github.com/jupyterlab/nbclassic/pull/36) ([@bollwyvl](https://github.com/bollwyvl)) * Update release instructions [#34](https://github.com/jupyterlab/nbclassic/pull/34) ([@jasongrout](https://github.com/jasongrout)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-09-29&to=2020-12-08&type=c)) [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Abollwyvl+updated%3A2020-09-29..2020-12-08&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ajasongrout+updated%3A2020-09-29..2020-12-08&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3AZsailer+updated%3A2020-09-29..2020-12-08&type=Issues) ## 0.2.3 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/61ea2a7...46bb6d5)) ### Merged PRs * Moves terminal websocket handling back to Jupyter Server [#33](https://github.com/jupyterlab/nbclassic/pull/33) ([@jasongrout](https://github.com/jasongrout)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-09-29&to=2020-09-29&type=c)) [@jasongrout](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ajasongrout+updated%3A2020-09-29..2020-09-29&type=Issues) ## 0.2.2 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/cff037e...61ea2a7)) ### Merged PRs * Add terminal and editor handlers [#31](https://github.com/jupyterlab/nbclassic/pull/31) ([@afshin](https://github.com/afshin)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-09-25&to=2020-09-29&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Aafshin+updated%3A2020-09-25..2020-09-29&type=Issues) ## 0.2.1 ([Full Changelog](https://github.com/jupyterlab/nbclassic/compare/09c8756...cff037e)) ### Merged PRs * Add setupbase.py to MANIFEST.in [#28](https://github.com/jupyterlab/nbclassic/pull/28) ([@jtpio](https://github.com/jtpio)) * Add LICENSE [#27](https://github.com/jupyterlab/nbclassic/pull/27) ([@jtpio](https://github.com/jtpio)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/nbclassic/graphs/contributors?from=2020-09-18&to=2020-09-25&type=c)) [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Fnbclassic+involves%3Ajtpio+updated%3A2020-09-18..2020-09-25&type=Issues) nbclassic-0.3.2/LICENSE000066400000000000000000000027771412106002600144670ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2020 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. nbclassic-0.3.2/MANIFEST.in000066400000000000000000000000451412106002600152020ustar00rootroot00000000000000include LICENSE include setupbase.py nbclassic-0.3.2/README.md000066400000000000000000000032421412106002600147250ustar00rootroot00000000000000# Jupyter Notebook as a Jupyter Server Extension ![Testing nbclassic](https://github.com/jupyterlab/nbclassic/workflows/Testing%20nbclassic/badge.svg) NBClassic runs the [Jupyter Notebook](https://github.com/jupyter/notebook) frontend on the Jupyter Server backend. This project prepares for a future where JupyterLab and other frontends switch to [Jupyter Server](https://github.com/jupyter/jupyter_server/) for their Python Web application backend. Using this package, users can launch Jupyter Notebook, JupyterLab and other frontends side-by-side on top of the new Python server backend. ## Basic Usage Install from PyPI: ``` > pip install nbclassic ``` This will automatically enable the extension in Jupyter Server. Launch directly: ``` > jupyter nbclassic ``` Alternatively, you can run Jupyter Server and visiting the `/tree` endpoint: ``` > jupyter server ``` ## Further Details 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 `NBClassicConfigShimMixin` 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 nbclassic.shim import NBClassicConfigShimMixin class MyApplication(NBClassicConfigShimMixin, ExtensionApp): ``` nbclassic-0.3.2/RELEASE.md000066400000000000000000000006131412106002600150470ustar00rootroot00000000000000To create a release, update the version number in `nbclassic/__version__.py`, then run the following: ``` git clean -dffx python setup.py sdist python setup.py bdist_wheel export script_version=`python setup.py --version 2>/dev/null` git commit -a -m "Release $script_version" git tag $script_version git push --all git push --tags pip install twine twine check dist/* twine upload dist/* ``` nbclassic-0.3.2/docs/000077500000000000000000000000001412106002600143755ustar00rootroot00000000000000nbclassic-0.3.2/docs/shimming.md000066400000000000000000000056461412106002600165450ustar00rootroot00000000000000# Transitioning from Jupyter Notebook to Jupyter Server. **A story about configuration** ## Trait path Steps to handling traits during a "Transition and Deprecation Period". 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 `"DeprecationWarning: this trait has moved"` **for the user**. 2. Migrate/write the trait to a new config file if it came from a config file. 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` and either `NotebookApp` or `ServerApp`, 1. Raise a warning—**for the extension developers**—that there's redundant traits. 2. Pass trait to Step 2 above. - If the argument is *not* a trait of `ExtensionApp`, but *is* a trait of either `NotebookApp` or `ServerApp` (i.e. the trait moved from `ExtensionApp` to `NotebookApp`/`ServerApp`): 1. Raise a `"DeprecationWarning: this trait has moved"` **for the user**. 2. Migrate/write the trait to a new config file if it came from a config file. 2. Pass trait to Step 2 above. - If the argument is *not* a trait of `ExtensionApp` and not a trait of either `NotebookApp` or `ServerApp`, raise a `"Trait not found."` error. ## How JupyterApp's Parse Config. **Valid for traitlets 4.3.x** Here's a snapshot of how JupyterApp's are initialized. The order of operations we care about goes as follows: 1. `argv` is parsed. 2. `parse_command_line()` is called and `argv` is passed to this method. 3. `load_config_file()` is called to load traits from various files. ```python # Pulled from Traitlets 4.3.2 @catch_config_error def initialize(self, argv=None): # don't hook up crash handler before parsing command-line if argv is None: argv = sys.argv[1:] if argv: subc = self._find_subcommand(argv[0]) if subc: self.argv = argv self.subcommand = subc return self.parse_command_line(argv) cl_config = deepcopy(self.config) if self._dispatching: return self.migrate_config() self.load_config_file() # enforce cl-opts override configfile opts: self.update_config(cl_config) if allow_insecure_writes: issue_insecure_write_warning() ``` ## Where do we add the steps above? nbclassic-0.3.2/jupyter_server_config.d/000077500000000000000000000000001412106002600203045ustar00rootroot00000000000000nbclassic-0.3.2/jupyter_server_config.d/nbclassic.json000066400000000000000000000001451412106002600231400ustar00rootroot00000000000000{ "ServerApp": { "jpserver_extensions": { "nbclassic": true } } }nbclassic-0.3.2/nbclassic/000077500000000000000000000000001412106002600154065ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/__init__.py000066400000000000000000000004541412106002600175220ustar00rootroot00000000000000from .notebookapp import NotebookApp def _jupyter_server_extension_paths(): return [ { 'module': 'nbclassic.notebookapp', 'app': NotebookApp, 'name': 'jupyter-nbclassic' }, { 'module': 'nbclassic.nbserver', } ]nbclassic-0.3.2/nbclassic/__version__.py000066400000000000000000000000261412106002600202370ustar00rootroot00000000000000__version__ = "0.3.2" nbclassic-0.3.2/nbclassic/edit/000077500000000000000000000000001412106002600163335ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/edit/__init__.py000066400000000000000000000000001412106002600204320ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/edit/handlers.py000066400000000000000000000020501412106002600205020ustar00rootroot00000000000000#encoding: utf-8 """Tornado handlers for the terminal emulator.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from tornado import web from jupyter_server.base.handlers import JupyterHandler, path_regex from jupyter_server.utils import url_escape from jupyter_server.extension.handler import ( ExtensionHandlerMixin, ExtensionHandlerJinjaMixin ) class EditorHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): """Render the text editor interface.""" @web.authenticated def get(self, path): path = path.strip('/') if not self.contents_manager.file_exists(path): raise web.HTTPError(404, u'File does not exist: %s' % path) basename = path.rsplit('/', 1)[-1] self.write(self.render_template('edit.html', file_path=url_escape(path), basename=basename, page_title=basename + " (editing)", ) ) default_handlers = [ (r"/edit%s" % path_regex, EditorHandler), ] nbclassic-0.3.2/nbclassic/nbserver.py000066400000000000000000000113201412106002600176030ustar00rootroot00000000000000import os import types import inspect from functools import wraps from jupyter_core.paths import jupyter_config_path from jupyter_server.services.config.manager import ConfigManager from traitlets.traitlets import is_trait 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("nbclassic") keys = ["nbclassic"] + 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["nbclassic"] pkg.link_point("jupyter-nbclassic", serverapp) point = pkg.extension_points["jupyter-nbclassic"] 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 nbclassic. " "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 nbclassic-0.3.2/nbclassic/notebook/000077500000000000000000000000001412106002600172265ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/notebook/__init__.py000066400000000000000000000000001412106002600213250ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/notebook/handlers.py000066400000000000000000000106441412106002600214050ustar00rootroot00000000000000"""Tornado handlers for the live notebook view. This is a fork from jupyter/notebook#6.x """ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from collections import namedtuple import os from tornado import web, gen HTTPError = web.HTTPError from jupyter_server.base.handlers import JupyterHandler from jupyter_server.extension.handler import ( ExtensionHandlerMixin, ExtensionHandlerJinjaMixin ) from jupyter_server.base.handlers import path_regex, FilesRedirectHandler from jupyter_server.utils import ( url_path_join, url_escape, ensure_async ) from jupyter_server.transutils import _i18n def get_frontend_exporters(): from nbconvert.exporters.base import get_export_names, get_exporter # name=exporter_name, display=export_from_notebook+extension ExporterInfo = namedtuple('ExporterInfo', ['name', 'display']) default_exporters = [ ExporterInfo(name='html', display='HTML (.html)'), ExporterInfo(name='latex', display='LaTeX (.tex)'), ExporterInfo(name='markdown', display='Markdown (.md)'), ExporterInfo(name='notebook', display='Notebook (.ipynb)'), ExporterInfo(name='pdf', display='PDF via LaTeX (.pdf)'), ExporterInfo(name='rst', display='reST (.rst)'), ExporterInfo(name='script', display='Script (.txt)'), ExporterInfo(name='slides', display='Reveal.js slides (.slides.html)') ] frontend_exporters = [] for name in get_export_names(): exporter_class = get_exporter(name) exporter_instance = exporter_class() ux_name = getattr(exporter_instance, 'export_from_notebook', None) super_uxname = getattr(super(exporter_class, exporter_instance), 'export_from_notebook', None) # Ensure export_from_notebook is explicitly defined & not inherited if ux_name is not None and ux_name != super_uxname: display = _i18n('{} ({})'.format(ux_name, exporter_instance.file_extension)) frontend_exporters.append(ExporterInfo(name, display)) # Ensure default_exporters are in frontend_exporters if not already # This protects against nbconvert versions lower than 5.5 names = set(exporter.name.lower() for exporter in frontend_exporters) for exporter in default_exporters: if exporter.name not in names: frontend_exporters.append(exporter) # Protect against nbconvert 5.5.0 python_exporter = ExporterInfo(name='python', display='python (.py)') if python_exporter in frontend_exporters: frontend_exporters.remove(python_exporter) # Protect against nbconvert 5.4.x template_exporter = ExporterInfo(name='custom', display='custom (.txt)') if template_exporter in frontend_exporters: frontend_exporters.remove(template_exporter) return sorted(frontend_exporters) class NotebookHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): @web.authenticated @gen.coroutine def get(self, path): """get renders the notebook template if a name is given, or redirects to the '/files/' handler if the name is not given.""" path = path.strip('/') cm = self.contents_manager # will raise 404 on not found try: model = yield ensure_async(cm.get(path, content=False)) except web.HTTPError as e: if e.status_code == 404 and 'files' in path.split('/'): # 404, but '/files/' in URL, let FilesRedirect take care of it yield FilesRedirectHandler.redirect_to_files(self, path) else: raise if model['type'] != 'notebook': # not a notebook, redirect to files yield FilesRedirectHandler.redirect_to_files(self, path) name = path.rsplit('/', 1)[-1] self.write(self.render_template('notebook.html', notebook_path=path, notebook_name=name, kill_kernel=False, mathjax_url=self.mathjax_url, mathjax_config=self.mathjax_config, get_frontend_exporters=get_frontend_exporters ) ) #----------------------------------------------------------------------------- # URL to handler mappings #----------------------------------------------------------------------------- default_handlers = [ (r"/notebooks%s" % path_regex, NotebookHandler), ] nbclassic-0.3.2/nbclassic/notebookapp.py000066400000000000000000000211651412106002600203060ustar00rootroot00000000000000# coding: utf-8 """A tornado based Jupyter notebook server.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import absolute_import, print_function import os import gettext import random import sys import warnings import gettext from jinja2 import Environment, FileSystemLoader from tornado.web import RedirectHandler import notebook from notebook import ( DEFAULT_STATIC_FILES_PATH, DEFAULT_TEMPLATE_PATH_LIST, __version__, ) from traitlets.config import Config from traitlets.config.application import boolean_flag from traitlets import ( Dict, Unicode, Integer, List, Bool, observe, default ) from ipython_genutils import py3compat from jupyter_core.paths import jupyter_path from jupyter_server.base.handlers import FileFindHandler from jupyter_server.utils import url_path_join # Try to load Notebook as an extension of the Jupyter Server from jupyter_server.extension.application import ( ExtensionApp, ExtensionAppJinjaMixin ) from jupyter_server.log import log_request from jupyter_server.transutils import _i18n from jupyter_server.serverapp import ( ServerApp, random_ports, load_handlers ) from jupyter_server.utils import url_path_join as ujoin from .terminal.handlers import TerminalHandler #----------------------------------------------------------------------------- # Module globals #----------------------------------------------------------------------------- _examples = """ jupyter nbclassic # start the notebook jupyter nbclassic --certfile=mycert.pem # use SSL/TLS certificate jupyter nbclassic password # enter a password to protect the server """ #----------------------------------------------------------------------------- # Aliases and Flags #----------------------------------------------------------------------------- flags = {} aliases = {} flags['no-browser']=( {'ServerApp' : {'open_browser' : False}}, _i18n("Don't open the notebook in a browser after startup.") ) flags['no-mathjax']=( {'NotebookApp' : {'enable_mathjax' : False}}, """Disable MathJax 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. """ ) flags['allow-root']=( {'ServerApp' : {'allow_root' : True}}, _i18n("Allow the notebook to be run from root user.") ) aliases.update({ 'ip': 'ServerApp.ip', 'port': 'ServerApp.port', 'port-retries': 'ServerApp.port_retries', #'transport': 'KernelManager.transport', 'keyfile': 'ServerApp.keyfile', 'certfile': 'ServerApp.certfile', 'client-ca': 'ServerApp.client_ca', 'notebook-dir': 'ServerApp.notebook_dir', 'browser': 'ServerApp.browser', #'gateway-url': 'GatewayClient.url', }) #----------------------------------------------------------------------------- # NotebookApp #----------------------------------------------------------------------------- from . import shim from . import traits class NotebookApp( shim.NBClassicConfigShimMixin, ExtensionAppJinjaMixin, ExtensionApp, traits.NotebookAppTraits, ): name = 'notebook' version = __version__ description = _i18n("""The Jupyter HTML Notebook. This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client.""") aliases = aliases flags = flags extension_url = "/tree" subcommands = {} default_url = Unicode("/tree").tag(config=True) # Override the default open_Browser trait in ExtensionApp, # setting it to True. open_browser = Bool( True, help="""Whether to open in a browser after starting. The specific browser used is platform dependent and determined by the python standard library `webbrowser` module, unless it is overridden using the --browser (ServerApp.browser) configuration option. """ ).tag(config=True) 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(d, 'custom') for d in ( self.config_dir, DEFAULT_STATIC_FILES_PATH) ] 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 @property def static_paths(self): """Rename trait in jupyter_server.""" return self.static_file_path @property def template_paths(self): """Rename trait for Jupyter Server.""" return self.template_file_path def _prepare_templates(self): super(NotebookApp, self)._prepare_templates() # Get translations from notebook package. base_dir = os.path.dirname(notebook.__file__) nbui = gettext.translation('nbui', localedir=os.path.join(base_dir, 'notebook/i18n'), fallback=True) self.jinja2_env.install_gettext_translations(nbui, newstyle=False) def initialize_settings(self): """Add settings to the tornado app.""" if self.ignore_minified_js: self.log.warning(_i18n("""The `ignore_minified_js` flag is deprecated and no longer works.""")) self.log.warning(_i18n("""Alternatively use `%s` when working on the notebook's Javascript and LESS""") % 'npm run build:watch') warnings.warn(_i18n("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"), DeprecationWarning) settings = dict( static_custom_path=self.static_custom_path, static_handler_args = { # don't cache custom.js 'no_cache_paths': [ url_path_join( self.serverapp.base_url, 'static', self.name, 'custom' ) ], }, ignore_minified_js=self.ignore_minified_js, mathjax_url=self.mathjax_url, mathjax_config=self.mathjax_config, nbextensions_path=self.nbextensions_path, ) self.settings.update(**settings) def initialize_handlers(self): """Load the (URL pattern, handler) tuples for each component.""" # Order matters. The first handler to match the URL will handle the request. handlers = [] # Add a redirect from /notebooks to /edit # for opening non-ipynb files in edit mode. handlers.append( ( rf"/{self.file_url_prefix}/((?!.*\.ipynb($|\?)).*)", RedirectHandler, {"url": self.serverapp.base_url+"edit/{0}"} ) ) # load extra services specified by users before default handlers for service in self.settings['extra_services']: handlers.extend(load_handlers(service)) handlers.extend(load_handlers('nbclassic.tree.handlers')) handlers.extend(load_handlers('nbclassic.notebook.handlers')) handlers.extend(load_handlers('nbclassic.edit.handlers')) # Add terminal handlers handlers.append( (r"/terminals/(\w+)", TerminalHandler) ) handlers.append( (r"/nbextensions/(.*)", FileFindHandler, { 'path': self.settings['nbextensions_path'], 'no_cache_paths': ['/'], # don't cache anything in nbextensions }), ) handlers.append( (r"/custom/(.*)", FileFindHandler, { 'path': self.settings['static_custom_path'], 'no_cache_paths': ['/'], # don't cache anything in nbextensions }), ) # Add new handlers to Jupyter server handlers. self.handlers.extend(handlers) #----------------------------------------------------------------------------- # Main entry point #----------------------------------------------------------------------------- main = launch_new_instance = NotebookApp.launch_instance nbclassic-0.3.2/nbclassic/shim.py000066400000000000000000000270341412106002600167260ustar00rootroot00000000000000import os import sys import re from functools import wraps from copy import deepcopy from traitlets import TraitError, HasTraits from traitlets.config.application import catch_config_error from traitlets.config.loader import ( KVArgParseConfigLoader, Config, ) from jupyter_core.application import JupyterApp from jupyter_server.serverapp import ServerApp from jupyter_server.extension.application import ExtensionApp from .traits import NotebookAppTraits NBAPP_AND_SVAPP_SHIM_MSG = lambda trait_name: ( "'{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, ) ) NBAPP_TO_SVAPP_SHIM_MSG = lambda trait_name: ( "'{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, ) ) EXTAPP_AND_NBAPP_AND_SVAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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 ) ) EXTAPP_AND_SVAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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 ) ) EXTAPP_AND_NBAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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 ) ) NOT_EXTAPP_NBAPP_AND_SVAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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 ) ) EXTAPP_TO_SVAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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 ) ) EXTAPP_TO_NBAPP_SHIM_MSG = lambda trait_name, extapp_name: ( "'{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") class NBClassicConfigShimMixin: """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 nbclassic-0.3.2/nbclassic/terminal/000077500000000000000000000000001412106002600172215ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/terminal/__init__.py000066400000000000000000000000001412106002600213200ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/terminal/handlers.py000066400000000000000000000014171412106002600213760ustar00rootroot00000000000000#encoding: utf-8 """Tornado handlers for the terminal emulator.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from tornado import web from jupyter_server.base.handlers import JupyterHandler from jupyter_server.extension.handler import ( ExtensionHandlerMixin, ExtensionHandlerJinjaMixin ) class TerminalHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): """Render the terminal interface.""" @web.authenticated def get(self, term_name): if term_name not in self.terminal_manager.terminals: self.terminal_manager.create(name=term_name) self.write(self.render_template('terminal.html', ws_path="terminals/websocket/%s" % term_name)) nbclassic-0.3.2/nbclassic/traits.py000066400000000000000000000117471412106002600173000ustar00rootroot00000000000000import os from traitlets import ( HasTraits, Dict, Unicode, Integer, List, Bool, observe, default ) from notebook import ( DEFAULT_STATIC_FILES_PATH, DEFAULT_TEMPLATE_PATH_LIST, __version__, ) 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 + [DEFAULT_STATIC_FILES_PATH] 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(d, 'custom') for d in ( self.config_dir, DEFAULT_STATIC_FILES_PATH) ] 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 + DEFAULT_TEMPLATE_PATH_LIST 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.")) ) nbclassic-0.3.2/nbclassic/tree/000077500000000000000000000000001412106002600163455ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/tree/__init__.py000066400000000000000000000000001412106002600204440ustar00rootroot00000000000000nbclassic-0.3.2/nbclassic/tree/handlers.py000066400000000000000000000060111412106002600205150ustar00rootroot00000000000000"""Tornado handlers for the tree view. This is a fork from jupyter/notebook#5.7.x """ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from tornado import web import os from jupyter_server.base.handlers import JupyterHandler from jupyter_server.extension.handler import ( ExtensionHandlerMixin, ExtensionHandlerJinjaMixin ) from jupyter_server.base.handlers import path_regex from jupyter_server.utils import url_path_join, url_escape class TreeHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): """Render the tree view, listing notebooks, etc.""" def generate_breadcrumbs(self, path): breadcrumbs = [(url_path_join(self.base_url, 'tree'), '')] parts = path.split('/') for i in range(len(parts)): if parts[i]: link = url_path_join(self.base_url, 'tree', url_escape(url_path_join(*parts[:i+1])), ) breadcrumbs.append((link, parts[i])) return breadcrumbs def generate_page_title(self, path): comps = path.split('/') if len(comps) > 3: for i in range(len(comps)-2): comps.pop(0) page_title = url_path_join(*comps) if page_title: return page_title+'/' else: return 'Home' @web.authenticated def get(self, path=''): path = path.strip('/') cm = self.contents_manager if cm.dir_exists(path=path): if cm.is_hidden(path) and not cm.allow_hidden: self.log.info("Refusing to serve hidden directory, via 404 Error") raise web.HTTPError(404) breadcrumbs = self.generate_breadcrumbs(path) page_title = self.generate_page_title(path) self.write(self.render_template('tree.html', page_title=page_title, notebook_path=path, breadcrumbs=breadcrumbs, terminals_available=self.settings['terminals_available'], server_root=self.settings['server_root_dir'], shutdown_button=self.settings.get('shutdown_button', False) )) elif cm.file_exists(path): # it's not a directory, we have redirecting to do model = cm.get(path, content=False) # redirect to /api/notebooks if it's a notebook, otherwise /api/files service = 'notebooks' if model['type'] == 'notebook' else 'files' url = url_path_join( self.base_url, service, url_escape(path), ) self.log.debug("Redirecting %s to %s", self.request.path, url) self.redirect(url) else: raise web.HTTPError(404) #----------------------------------------------------------------------------- # URL to handler mappings #----------------------------------------------------------------------------- default_handlers = [ (r"/tree%s" % path_regex, TreeHandler), (r"/tree", TreeHandler), ] nbclassic-0.3.2/setup.cfg000066400000000000000000000000541412106002600152650ustar00rootroot00000000000000[tool:pytest] addopts = --color=yes -s nbclassic-0.3.2/setup.py000066400000000000000000000042241412106002600151610ustar00rootroot00000000000000import os from setuptools import setup, find_packages from setupbase import create_cmdclass NAME = 'nbclassic' about = {} here = os.path.abspath(os.path.dirname(__file__)) project_slug = NAME.lower().replace("-", "_").replace(" ", "_") with open(os.path.join(here, project_slug, '__version__.py')) as f: exec(f.read(), about) with open("README.md", "r") as fh: long_description = fh.read() here = os.path.abspath(os.path.dirname(__file__)) # Handle datafiles cmdclass = create_cmdclass( data_files_spec=[( 'etc/jupyter/jupyter_server_config.d', 'jupyter_server_config.d', '*.json' )] ) setup_args = dict( name = NAME, description = 'Jupyter Notebook as a Jupyter Server Extension.', long_description = long_description, long_description_content_type="text/markdown", version = about['__version__'], packages = find_packages(exclude=['tests*']), author = 'Jupyter Development Team', author_email = 'jupyter@googlegroups.com', url = 'http://jupyter.org', license = 'BSD', platforms = "Linux, Mac OS X, Windows", keywords = ['ipython', 'jupyter'], classifiers = [ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', ], cmdclass = cmdclass, zip_safe=False, python_requires='>=3.6', include_package_data=True, install_requires = [ 'jupyter_server~=1.8', 'notebook<7', ], entry_points = { 'console_scripts': [ 'jupyter-nbclassic = nbclassic.notebookapp:main' ] }, extras_require = { 'test': [ 'pytest', 'pytest-tornasync', 'pytest-console-scripts' ], }, ) if __name__ == '__main__': setup(**setup_args) nbclassic-0.3.2/setupbase.py000066400000000000000000000547001412106002600160200ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ This file originates from the 'jupyter-packaging' package, and contains a set of useful utilities for including npm packages within a Python package. """ from collections import defaultdict from os.path import join as pjoin import io import os import functools import pipes import re import shlex import subprocess import sys # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly # update it when the contents of directories change. if os.path.exists('MANIFEST'): os.remove('MANIFEST') from setuptools import Command from setuptools.command.build_py import build_py from setuptools.command.sdist import sdist from distutils import log from setuptools.command.develop import develop from setuptools.command.bdist_egg import bdist_egg try: from wheel.bdist_wheel import bdist_wheel except ImportError: bdist_wheel = None if sys.platform == 'win32': from subprocess import list2cmdline else: def list2cmdline(cmd_list): return ' '.join(map(pipes.quote, cmd_list)) __version__ = '0.5.0' # --------------------------------------------------------------------------- # Top Level Variables # --------------------------------------------------------------------------- SEPARATORS = os.sep if os.altsep is None else os.sep + os.altsep if "--skip-npm" in sys.argv: print("Skipping npm install as requested.") skip_npm = True sys.argv.remove("--skip-npm") else: skip_npm = False # --------------------------------------------------------------------------- # Public Functions # --------------------------------------------------------------------------- def get_version(file, name='__version__'): """Get the version of the package from the given file by executing it and extracting the given `name`. """ path = os.path.realpath(file) version_ns = {} with io.open(path, encoding="utf8") as f: exec(f.read(), {}, version_ns) return version_ns[name] def ensure_python(specs): """Given a list of range specifiers for python, ensure compatibility. """ if not isinstance(specs, (list, tuple)): specs = [specs] v = sys.version_info part = '%s.%s' % (v.major, v.minor) for spec in specs: if part == spec: return try: if eval(part + spec): return except SyntaxError: pass raise ValueError('Python version %s unsupported' % part) def find_packages(top): """ Find all of the packages. """ import warnings warnings.warn( 'Deprecated, please use setuptools.find_packages', category=DeprecationWarning ) from setuptools import find_packages as fp return fp(top) def update_package_data(distribution): """update build_py options to get package_data changes""" build_py = distribution.get_command_obj('build_py') build_py.finalize_options() class bdist_egg_disabled(bdist_egg): """Disabled version of bdist_egg Prevents setup.py install performing setuptools' default easy_install, which it should never ever do. """ def run(self): sys.exit("Aborting implicit building of eggs. Use `pip install .` " " to install from source.") def create_cmdclass(prerelease_cmd=None, package_data_spec=None, data_files_spec=None): """Create a command class with the given optional prerelease class. Parameters ---------- prerelease_cmd: (name, Command) tuple, optional The command to run before releasing. package_data_spec: dict, optional A dictionary whose keys are the dotted package names and whose values are a list of glob patterns. data_files_spec: list, optional A list of (path, dname, pattern) tuples where the path is the `data_files` install path, dname is the source directory, and the pattern is a glob pattern. Notes ----- We use specs so that we can find the files *after* the build command has run. The package data glob patterns should be relative paths from the package folder containing the __init__.py file, which is given as the package name. e.g. `dict(foo=['./bar/*', './baz/**'])` The data files directories should be absolute paths or relative paths from the root directory of the repository. Data files are specified differently from `package_data` because we need a separate path entry for each nested folder in `data_files`, and this makes it easier to parse. e.g. `('share/foo/bar', 'pkgname/bizz, '*')` """ wrapped = [prerelease_cmd] if prerelease_cmd else [] if package_data_spec or data_files_spec: wrapped.append('handle_files') wrapper = functools.partial(_wrap_command, wrapped) handle_files = _get_file_handler(package_data_spec, data_files_spec) develop_handler = _get_develop_handler() if 'bdist_egg' in sys.argv: egg = wrapper(bdist_egg, strict=True) else: egg = bdist_egg_disabled is_repo = os.path.exists('.git') cmdclass = dict( build_py=wrapper(build_py, strict=is_repo), bdist_egg=egg, sdist=wrapper(sdist, strict=True), handle_files=handle_files, ) if bdist_wheel: cmdclass['bdist_wheel'] = wrapper(bdist_wheel, strict=True) cmdclass['develop'] = wrapper(develop_handler, strict=True) return cmdclass def command_for_func(func): """Create a command that calls the given function.""" class FuncCommand(BaseCommand): def run(self): func() update_package_data(self.distribution) return FuncCommand def run(cmd, **kwargs): """Echo a command before running it.""" log.info('> ' + list2cmdline(cmd)) kwargs.setdefault('shell', os.name == 'nt') if not isinstance(cmd, (list, tuple)) and os.name != 'nt': cmd = shlex.split(cmd) cmd_path = which(cmd[0]) if not cmd_path: sys.exit("Aborting. Could not find cmd (%s) in path. " "If command is not expected to be in user's path, " "use an absolute path." % cmd[0]) cmd[0] = cmd_path return subprocess.check_call(cmd, **kwargs) def is_stale(target, source): """Test whether the target file/directory is stale based on the source file/directory. """ if not os.path.exists(target): return True target_mtime = recursive_mtime(target) or 0 return compare_recursive_mtime(source, cutoff=target_mtime) class BaseCommand(Command): """Empty command because Command needs subclasses to override too much""" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def get_inputs(self): return [] def get_outputs(self): return [] def combine_commands(*commands): """Return a Command that combines several commands.""" class CombinedCommand(Command): user_options = [] def initialize_options(self): self.commands = [] for C in commands: self.commands.append(C(self.distribution)) for c in self.commands: c.initialize_options() def finalize_options(self): for c in self.commands: c.finalize_options() def run(self): for c in self.commands: c.run() return CombinedCommand def compare_recursive_mtime(path, cutoff, newest=True): """Compare the newest/oldest mtime for all files in a directory. Cutoff should be another mtime to be compared against. If an mtime that is newer/older than the cutoff is found it will return True. E.g. if newest=True, and a file in path is newer than the cutoff, it will return True. """ if os.path.isfile(path): mt = mtime(path) if newest: if mt > cutoff: return True elif mt < cutoff: return True for dirname, _, filenames in os.walk(path, topdown=False): for filename in filenames: mt = mtime(pjoin(dirname, filename)) if newest: # Put outside of loop? if mt > cutoff: return True elif mt < cutoff: return True return False def recursive_mtime(path, newest=True): """Gets the newest/oldest mtime for all files in a directory.""" if os.path.isfile(path): return mtime(path) current_extreme = None for dirname, dirnames, filenames in os.walk(path, topdown=False): for filename in filenames: mt = mtime(pjoin(dirname, filename)) if newest: # Put outside of loop? if mt >= (current_extreme or mt): current_extreme = mt elif mt <= (current_extreme or mt): current_extreme = mt return current_extreme def mtime(path): """shorthand for mtime""" return os.stat(path).st_mtime def install_npm(path=None, build_dir=None, source_dir=None, build_cmd='build', force=False, npm=None): """Return a Command for managing an npm installation. Note: The command is skipped if the `--skip-npm` flag is used. Parameters ---------- path: str, optional The base path of the node package. Defaults to the current directory. build_dir: str, optional The target build directory. If this and source_dir are given, the JavaScript will only be build if necessary. source_dir: str, optional The source code directory. build_cmd: str, optional The npm command to build assets to the build_dir. npm: str or list, optional. The npm executable name, or a tuple of ['node', executable]. """ class NPM(BaseCommand): description = 'install package.json dependencies using npm' def run(self): if skip_npm: log.info('Skipping npm-installation') return node_package = path or os.path.abspath(os.getcwd()) node_modules = pjoin(node_package, 'node_modules') is_yarn = os.path.exists(pjoin(node_package, 'yarn.lock')) npm_cmd = npm if npm is None: if is_yarn: npm_cmd = ['yarn'] else: npm_cmd = ['npm'] if not which(npm_cmd[0]): log.error("`{0}` unavailable. If you're running this command " "using sudo, make sure `{0}` is available to sudo" .format(npm_cmd[0])) return if force or is_stale(node_modules, pjoin(node_package, 'package.json')): log.info('Installing build dependencies with npm. This may ' 'take a while...') run(npm_cmd + ['install'], cwd=node_package) if build_dir and source_dir and not force: should_build = is_stale(build_dir, source_dir) else: should_build = True if should_build: run(npm_cmd + ['run', build_cmd], cwd=node_package) return NPM def ensure_targets(targets): """Return a Command that checks that certain files exist. Raises a ValueError if any of the files are missing. Note: The check is skipped if the `--skip-npm` flag is used. """ class TargetsCheck(BaseCommand): def run(self): if skip_npm: log.info('Skipping target checks') return missing = [t for t in targets if not os.path.exists(t)] if missing: raise ValueError(('missing files: %s' % missing)) return TargetsCheck # `shutils.which` function copied verbatim from the Python-3.3 source. def which(cmd, mode=os.F_OK | os.X_OK, path=None): """Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file. `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path. """ # Check that a given file can be accessed with the correct mode. # Additionally check that `file` is not a directory, as on Windows # directories pass the os.access check. def _access_check(fn, mode): return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn)) # Short circuit. If we're given a full path which matches the mode # and it exists, we're done here. if _access_check(cmd, mode): return cmd path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep) if sys.platform == "win32": # The current directory takes precedence on Windows. if os.curdir not in path: path.insert(0, os.curdir) # PATHEXT is necessary to check on Windows. pathext = os.environ.get("PATHEXT", "").split(os.pathsep) # See if the given file matches any of the expected path extensions. # This will allow us to short circuit when given "python.exe". matches = [cmd for ext in pathext if cmd.lower().endswith(ext.lower())] # If it does match, only test that one, otherwise we have to try # others. files = [cmd] if matches else [cmd + ext.lower() for ext in pathext] else: # On other platforms you don't have things like PATHEXT to tell you # what file suffixes are executable, so just pass on cmd as-is. files = [cmd] seen = set() for dir in path: dir = os.path.normcase(dir) if dir not in seen: seen.add(dir) for thefile in files: name = os.path.join(dir, thefile) if _access_check(name, mode): return name return None # --------------------------------------------------------------------------- # Private Functions # --------------------------------------------------------------------------- def _wrap_command(cmds, cls, strict=True): """Wrap a setup command Parameters ---------- cmds: list(str) The names of the other commands to run prior to the command. strict: boolean, optional Whether to raise errors when a pre-command fails. """ class WrappedCommand(cls): def run(self): if not getattr(self, 'uninstall', None): try: [self.run_command(cmd) for cmd in cmds] except Exception: if strict: raise else: pass # update package data update_package_data(self.distribution) result = cls.run(self) return result return WrappedCommand def _get_file_handler(package_data_spec, data_files_spec): """Get a package_data and data_files handler command. """ class FileHandler(BaseCommand): def run(self): package_data = self.distribution.package_data package_spec = package_data_spec or dict() for (key, patterns) in package_spec.items(): package_data[key] = _get_package_data(key, patterns) self.distribution.data_files = _get_data_files( data_files_spec, self.distribution.data_files ) return FileHandler def _get_develop_handler(): """Get a handler for the develop command""" class _develop(develop): def install_for_development(self): super(_develop, self).install_for_development() self.run_command('handle_files') for _, filenames in self.distribution.data_files: for filename in filenames: target = os.path.join(sys.prefix, filename) self.mkpath(os.path.dirname(target)) outf, copied = self.copy_file(filename, target) return _develop def _glob_pjoin(*parts): """Join paths for glob processing""" if parts[0] in ('.', ''): parts = parts[1:] return pjoin(*parts).replace(os.sep, '/') def _get_data_files(data_specs, existing, top=None): """Expand data file specs into valid data files metadata. Parameters ---------- data_specs: list of tuples See [create_cmdclass] for description. existing: list of tuples The existing distribution data_files metadata. Returns ------- A valid list of data_files items. """ if top is None: top = os.path.abspath(os.getcwd()) # Extract the existing data files into a staging object. file_data = defaultdict(list) for (path, files) in existing or []: file_data[path] = files # Extract the files and assign them to the proper data # files path. for (path, dname, pattern) in data_specs or []: if os.path.isabs(dname): dname = os.path.relpath(dname, top) dname = dname.replace(os.sep, '/') offset = 0 if dname in ('.', '') else len(dname) + 1 files = _get_files(_glob_pjoin(dname, pattern), top=top) for fname in files: # Normalize the path. root = os.path.dirname(fname) full_path = _glob_pjoin(path, root[offset:]) if full_path.endswith('/'): full_path = full_path[:-1] file_data[full_path].append(fname) # Construct the data files spec. data_files = [] for (path, files) in file_data.items(): data_files.append((path, files)) return data_files def _get_files(file_patterns, top=None): """Expand file patterns to a list of paths. Parameters ----------- file_patterns: list or str A list of glob patterns for the data file locations. The globs can be recursive if they include a `**`. They should be relative paths from the top directory or absolute paths. top: str the directory to consider for data files Note: Files in `node_modules` are ignored. """ if top is None: top = os.path.abspath(os.getcwd()) if not isinstance(file_patterns, (list, tuple)): file_patterns = [file_patterns] for i, p in enumerate(file_patterns): if os.path.isabs(p): file_patterns[i] = os.path.relpath(p, top) matchers = [_compile_pattern(p) for p in file_patterns] files = set() for root, dirnames, filenames in os.walk(top): # Don't recurse into node_modules if 'node_modules' in dirnames: dirnames.remove('node_modules') for m in matchers: for filename in filenames: fn = os.path.relpath(_glob_pjoin(root, filename), top) fn = fn.replace(os.sep, '/') if m(fn): files.add(fn) return list(files) def _get_package_data(root, file_patterns=None): """Expand file patterns to a list of `package_data` paths. Parameters ----------- root: str The relative path to the package root from the current dir. file_patterns: list or str, optional A list of glob patterns for the data file locations. The globs can be recursive if they include a `**`. They should be relative paths from the root or absolute paths. If not given, all files will be used. Note: Files in `node_modules` are ignored. """ if file_patterns is None: file_patterns = ['*'] return _get_files(file_patterns, _glob_pjoin(os.path.abspath(os.getcwd()), root)) def _compile_pattern(pat, ignore_case=True): """Translate and compile a glob pattern to a regular expression matcher.""" if isinstance(pat, bytes): pat_str = pat.decode('ISO-8859-1') res_str = _translate_glob(pat_str) res = res_str.encode('ISO-8859-1') else: res = _translate_glob(pat) flags = re.IGNORECASE if ignore_case else 0 return re.compile(res, flags=flags).match def _iexplode_path(path): """Iterate over all the parts of a path. Splits path recursively with os.path.split(). """ (head, tail) = os.path.split(path) if not head or (not tail and head == path): if head: yield head if tail or not head: yield tail return for p in _iexplode_path(head): yield p yield tail def _translate_glob(pat): """Translate a glob PATTERN to a regular expression.""" translated_parts = [] for part in _iexplode_path(pat): translated_parts.append(_translate_glob_part(part)) os_sep_class = '[%s]' % re.escape(SEPARATORS) res = _join_translated(translated_parts, os_sep_class) return '(?ms){res}\\Z'.format(res=res) def _join_translated(translated_parts, os_sep_class): """Join translated glob pattern parts. This is different from a simple join, as care need to be taken to allow ** to match ZERO or more directories. """ res = '' for part in translated_parts[:-1]: if part == '.*': # drop separator, since it is optional # (** matches ZERO or more dirs) res += part else: res += part + os_sep_class if translated_parts[-1] == '.*': # Final part is ** res += '.+' # Follow stdlib/git convention of matching all sub files/directories: res += '({os_sep_class}?.*)?'.format(os_sep_class=os_sep_class) else: res += translated_parts[-1] return res def _translate_glob_part(pat): """Translate a glob PATTERN PART to a regular expression.""" # Code modified from Python 3 standard lib fnmatch: if pat == '**': return '.*' i, n = 0, len(pat) res = [] while i < n: c = pat[i] i = i + 1 if c == '*': # Match anything but path separators: res.append('[^%s]*' % SEPARATORS) elif c == '?': res.append('[^%s]?' % SEPARATORS) elif c == '[': j = i if j < n and pat[j] == '!': j = j + 1 if j < n and pat[j] == ']': j = j + 1 while j < n and pat[j] != ']': j = j + 1 if j >= n: res.append('\\[') else: stuff = pat[i:j].replace('\\', '\\\\') i = j + 1 if stuff[0] == '!': stuff = '^' + stuff[1:] elif stuff[0] == '^': stuff = '\\' + stuff res.append('[%s]' % stuff) else: res.append(re.escape(c)) return ''.join(res) nbclassic-0.3.2/tests/000077500000000000000000000000001412106002600146075ustar00rootroot00000000000000nbclassic-0.3.2/tests/__init__.py000066400000000000000000000000001412106002600167060ustar00rootroot00000000000000nbclassic-0.3.2/tests/confs/000077500000000000000000000000001412106002600157175ustar00rootroot00000000000000nbclassic-0.3.2/tests/confs/jupyter_my_ext_config.py000066400000000000000000000000371412106002600227050ustar00rootroot00000000000000c.MyExt.hello = 'My extension' nbclassic-0.3.2/tests/confs/jupyter_notebook_config.py000066400000000000000000000001511412106002600232150ustar00rootroot00000000000000c.NotebookApp.allow_credentials = False c.NotebookApp.port = 8889 c.NotebookApp.password_required = True nbclassic-0.3.2/tests/confs/jupyter_server_config.py000066400000000000000000000000301412106002600226770ustar00rootroot00000000000000c.ServerApp.port = 7774 nbclassic-0.3.2/tests/conftest.py000066400000000000000000000025061412106002600170110ustar00rootroot00000000000000import io import logging import pytest from traitlets import default from nbclassic.notebookapp import NotebookApp pytest_plugins = ["jupyter_server.pytest_plugin"] @pytest.fixture def nbapp_log(): """An io stream with the NotebookApp's logging output""" stream = io.StringIO() return stream @pytest.fixture(autouse=True) def notebookapp_logcapture(monkeypatch, nbapp_log): """""" @default('log') def _log_default(self): """Start logging for this application. The default is to log to stderr using a StreamHandler, if no default handler already exists. The log level starts at logging.WARN, but this can be adjusted by setting the ``log_level`` attribute. """ log = super(self.__class__, self)._log_default() _log_handler = logging.StreamHandler(nbapp_log) _log_formatter = self._log_formatter_cls( fmt=self.log_format, datefmt=self.log_datefmt ) _log_handler.setFormatter(_log_formatter) log.addHandler(_log_handler) return log monkeypatch.setattr(NotebookApp, '_log_default', _log_default) return _log_default @pytest.fixture def jp_server_config(): return { "ServerApp": { "jpserver_extensions": { "nbclassic": True } } } nbclassic-0.3.2/tests/shim/000077500000000000000000000000001412106002600155475ustar00rootroot00000000000000nbclassic-0.3.2/tests/shim/__init__.py000066400000000000000000000000001412106002600176460ustar00rootroot00000000000000nbclassic-0.3.2/tests/shim/mockextension.py000066400000000000000000000015051412106002600210100ustar00rootroot00000000000000 from traitlets import ( Unicode, Bool, ) from jupyter_server.extension.application import ExtensionApp from nbclassic import shim def _jupyter_server_extension_points(): return [ { "module": "tests.shim.mockextension", "app": MockExtensionApp } ] class MockExtensionApp( shim.NBClassicConfigShimMixin, 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)nbclassic-0.3.2/tests/shim/test_extension.py000066400000000000000000000107201412106002600211740ustar00rootroot00000000000000 import io import logging import pytest from traitlets import default from .mockextension import MockExtensionApp from nbclassic import shim @pytest.fixture def extapp_log(): """An io stream with the NotebookApp's logging output""" stream = io.StringIO() return stream @pytest.fixture(autouse=True) def extapp_logcapture(monkeypatch, extapp_log): """""" @default('log') def _log_default(self): """Start logging for this application. The default is to log to stderr using a StreamHandler, if no default handler already exists. The log level starts at logging.WARN, but this can be adjusted by setting the ``log_level`` attribute. """ log = super(self.__class__, self)._log_default() _log_handler = logging.StreamHandler(extapp_log) _log_formatter = self._log_formatter_cls( fmt=self.log_format, datefmt=self.log_datefmt ) _log_handler.setFormatter(_log_formatter) log.addHandler(_log_handler) return log monkeypatch.setattr(MockExtensionApp, '_log_default', _log_default) return _log_default @pytest.fixture def jp_server_config(): return { "ServerApp": { "jpserver_extensions": { "nbclassic": True, "tests.shim.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( extensionapp, extapp_log, jp_argv, trait_name, trait_value ): log = extapp_log.getvalue() # 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( extensionapp, extapp_log, jp_argv, trait_name, trait_value ): log = extapp_log.getvalue() # 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( extensionapp, extapp_log, jp_argv, trait_name, trait_value ): log = extapp_log.getvalue() # 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( extensionapp, extapp_log, jp_argv, trait_name, trait_value ): log = extapp_log.getvalue() # 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( extensionapp, extapp_log, jp_argv, trait_name, trait_value ): log = extapp_log.getvalue() # Verify a shim warning appeared. log_msg = shim.EXTAPP_TO_NBAPP_SHIM_MSG(trait_name, 'MockExtensionApp') assert log_msg in log nbclassic-0.3.2/tests/shim/test_nbclassic.py000066400000000000000000000044471412106002600211320ustar00rootroot00000000000000import pytest from nbclassic import shim @pytest.fixture def jp_server_config(): return { "ServerApp": { "jpserver_extensions": { "nbclassic": True, } } } @pytest.fixture def nbclassic(jp_serverapp): return jp_serverapp.extension_manager.extension_points["notebook"].app def list_test_params(param_input): params = [] for test in param_input: name, value = test[0], test[1] option = ( '--NotebookApp.' '{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([ ('jinja_environment_options', {}), ('jinja_template_vars', {}), ('extra_template_paths', []), ('quit_button', True), ]) ) def test_NBAPP_AND_SVAPP_SHIM_MSG( nbclassic, nbapp_log, jp_argv, trait_name, trait_value ): log = nbapp_log.getvalue() # Verify a shim warning appeared. assert shim.NBAPP_AND_SVAPP_SHIM_MSG(trait_name) in log # Verify the trait was updated. assert getattr(nbclassic, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ ('allow_origin', ''), ('allow_origin_pat', ''), ('allow_credentials', False), # ('allow_root', False) # This trait is hardcoded by jupyter-server pytest extension. ]) ) def test_NBAPP_TO_SVAPP_SHIM_MSG( jp_serverapp, nbapp_log, jp_argv, trait_name, trait_value ): # Expected log message. log = nbapp_log.getvalue() expected_msg = shim.NBAPP_TO_SVAPP_SHIM_MSG(trait_name) # Verify a warning message was raised assert expected_msg in log # Verify that trait changed. assert getattr(jp_serverapp, trait_name) == trait_value @pytest.mark.parametrize( 'jp_argv,trait_name,trait_value', list_test_params([ #('ignore_minified_js', True), ('enable_mathjax', False), ('mathjax_config', 'TEST'), ('mathjax_url', 'TEST') ]) ) def test_nbclassic_traits_pass_shim( nbclassic, jp_argv, trait_name, trait_value ): assert getattr(nbclassic, trait_name) == trait_value nbclassic-0.3.2/tests/test_notebookapp.py000066400000000000000000000017241412106002600205450ustar00rootroot00000000000000"""Basic tests for the notebook handlers. """ import pytest @pytest.fixture def notebooks(jp_create_notebook): nbpaths = ( 'notebook1.ipynb', 'nbclassic_test_notebooks/notebook2.ipynb', 'nbclassic_test_notebooks/level2/notebook3.ipynb' ) for nb in nbpaths: jp_create_notebook(nb) return nbpaths async def test_tree_handler(notebooks, jp_fetch): r = await jp_fetch('tree', 'nbclassic_test_notebooks') assert r.code == 200 # Check that the tree template is loaded html = r.body.decode() assert "Files" in html assert "Running" in html assert "Clusters" in html async def test_notebook_handler(notebooks, jp_fetch): for nbpath in notebooks: r = await jp_fetch('notebooks', nbpath) assert r.code == 200 # Check that the notebook template is loaded html = r.body.decode() assert "Menu" in html assert "Kernel" in html assert nbpath in html