diff --git a/docs/source/developers/basic-extension.rst b/docs/source/developers/basic-extension.rst index b67b926d06..5affbeb1d4 100644 --- a/docs/source/developers/basic-extension.rst +++ b/docs/source/developers/basic-extension.rst @@ -1,2 +1,298 @@ +========================= +Jupyter Server Extensions +========================= + +A Jupyter Server extension is typically a module or package that extends to Server’s REST API/endpoints—i.e. adds extra request handlers to Server’s Tornado Web Application. + Authoring a basic server extension -================================== \ No newline at end of file +================================== + +The simplest way to write a Jupyter Server extension is to write an extension module with a ``_load_jupyter_server_extension`` function. This function should take a single argument, an instance of the ``ServerApp``. + + +.. code-block:: python + + def _load_jupyter_server_extension(serverapp): + """ + This function is called when the extension is loaded. + """ + pass + + +Adding extension endpoints +-------------------------- + +The easiest way to add endpoints and handle incoming requests is to subclass the ``JupyterHandler`` (which itself is a subclass of Tornado's ``RequestHandler``). + +.. code-block:: python + + from jupyter_server.base.handlers import JupyterHandler + + class MyExtensionHandler(JupyterHandler): + + def get(self): + ... + + def post(self): + ... + + +Then add this handler to Jupyter Server's Web Application through the ``_load_jupyter_server_extension`` function. + +.. code-block:: python + + def _load_jupyter_server_extension(serverapp): + """ + This function is called when the extension is loaded. + """ + handlers = [ + ('/myextension/hello', MyExtensionHandler) + ] + serverapp.web_app.add_handlers('.*$', handlers) + + + +Making an extension discoverable +-------------------------------- + +To make this extension discoverable to Jupyter Server, there are two steps. First, the extension module must define a ``_jupyter_server_extension_paths()`` function that returns some metadata about the extension entry-points in the module. This informs Jupyter Server what type of extension is being loaded and where to find the ``_load_jupyter_server_extension``. + +.. code-block:: python + + def _jupyter_server_extension_paths(): + """ + Returns a list of dictionaries with metadata describing + where to find the `_load_jupyter_server_extension` function. + """ + return [ + { + "module": "my_extension" + } + ] + +Second, the extension must be listed in the user’s ``jpserver_extensions`` config trait. This can be manually added by users in their ``jupyter_server_config.py`` file: + +.. code-block:: python + + c.ServerApp.jpserver_extensions = { + "my_extension": True + } + +Alternatively, an extension can automatically enable itself by creating the following JSON in the jupyter_server_config.d directory on installation. See XX for more details. + +.. code-block:: python + + { + "ServerApp": { + "jpserver_extensions": { + "my_extension": true + } + } + } + + +Authoring a configurable extension application +============================================== + +Some extensions are full-fledged client applications that sit on top of the Jupyter Server. For example, `JupyterLab `_ is a server extension. It can be launched from the command line, configured by CLI or config files, and serves+loads static assets behind the server (i.e. html templates, Javascript, etc.) + +Jupyter Server offers a convenient base class, ``ExtensionsApp``, that handles most of the boilerplate code for building such extensions. + +Anatomy of an ``ExtensionApp`` +------------------------------ + +An ExtensionApp: + + - has traits. + - is configurable (from file or CLI) + - has a name (see the ``extension_name`` trait). + - has an entrypoint, ``jupyter ``. + - can server static content from the ``/static//`` endpoint. + - can add new endpoints to the Jupyter Server. + +The basic structure of an ExtensionApp is shown below: + +.. code-block:: python + + from jupyter_server.extension.application import ExtensionApp + + + class MyExtensionApp(ExtensionApp): + + # -------------- Required traits -------------- + extension_name = "myextension" + extension_url = "/myextension" + load_other_extensions = True + + # --- ExtensionApp traits you can configure --- + static_paths = [...] + template_paths = [...] + settings = {...} + handlers = [...] + + # ----------- add custom traits below --------- + ... + + def initialize_settings(self): + ... + # Update the self.settings trait to pass extra + # settings to the underlying Tornado Web Application. + self.settings.update({'':...}) + + def initialize_handlers(self): + ... + # Extend the self.handlers trait + self.handlers.extend(...) + + def initialize_templates(self): + ... + # Change the jinja templating environment + + +The ``ExtensionApp`` uses the following methods and properties to connect your extension to the Jupyter server. You do no need to define a ``_load_jupyter_server_extension`` function for these apps. Instead, overwrite the pieces below to add your custom settings, handlers and templates: + +Methods + +* ``initialize_setting()``: adds custom settings to the Tornado Web Application. +* ``initialize_handlers()``: appends handlers to the Tornado Web Application. +* ``initialize_templates()``: initialize the templating engine (e.g. jinja2) for your frontend. + +Properties + +* ``extension_name``: the name of the extension +* ``extension_url``: the default url for this extension—i.e. the landing page for this extension when launched from the CLI. +* ``load_other_extensions``: a boolean enabling/disabling other extensions when launching this extension directly. + +``ExtensionApp`` request handlers +--------------------------------- + +``ExtensionApp`` Request Handlers have a few extra properties. + +* ``config``: the ExtensionApp's config object. +* ``server_config``: the ServerApp's config object. +* ``extension_name``: the name of the extension to which this handler is linked. +* ``static_url()``: a method that returns the url to static files (prefixed with ``/static/``). + +Jupyter Server provides a convenient mixin class for adding these properties to any ``JupyterHandler``. For example, the basic server extension handler in the section above becomes: + +.. code-block:: python + + from jupyter_server.base.handlers import JupyterHandler + from jupyter_server.extension.handler import ExtensionHandlerMixin + + + class MyExtensionHandler(ExtensionHandlerMixin, JupyterHandler): + + def get(self): + ... + + def post(self): + ... + + +Jinja templating from frontend extensions +----------------------------------------- + +Many Jupyter frontend applications use Jinja for basic HTML templating. Since this is common enough, Jupyter Server provides some extra mixin that integrate Jinja with Jupyter server extensions. + +Use ``ExtensionAppJinjaMixin`` to automatically add a Jinja templating environment to an ``ExtensionApp``. This adds a ``_jinja2_env`` setting to Tornado Web Server's settings that be be used by request handlers. + +.. code-block:: python + + + from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin + + + class MyExtensionApp(ExtensionAppJinjaMixin, ExtensionApp): + ... + + +Pair the example above with ``ExtensionHandlers`` that also inherit the ``ExtensionHandlerJinjaMixin`` mixin. This will automatically load HTML templates from the Jinja templating environment created by the ``ExtensionApp``. + + +.. code-block:: python + + + from jupyter_server.base.handlers import JupyterHandler + from jupyter_server.extension.handler import ( + ExtensionHandlerMixin, + ExtensionHandlerJinjaMixin + ) + + class MyExtensionHandler( + ExtensionHandlerMixin, + ExtensionHandlerJinjaMixin, + JupyterHandler + ): + + def get(self): + ... + + def post(self): + ... + + +.. note:: The mixin classes in this example must come before the base classes, ``ExtensionApp`` and ``ExtensionHandler``. + + +Making an ``ExtensionApp`` discoverable +--------------------------------------- + +To make an ``ExtensionApp`` discoverable by Jupyter Server, add the ``app`` key+value pair to the ``_jupyter_server_extension_paths()`` function example above: + +.. code-block:: python + + from myextension import MyExtensionApp + + + def _jupyter_server_extension_paths(): + """ + Returns a list of dictionaries with metadata describing + where to find the `_load_jupyter_server_extension` function. + """ + return [ + { + "module": "myextension", + "app": MyExtensionApp + } + ] + + +Launching an ``ExtensionApp`` +----------------------------- + +To launch the application, simply call the ``ExtensionApp``'s ``launch_instance`` method. + +.. code-block:: python + + launch_instance = MyFrontend.launch_instance + launch_instance() + + +To make your extension executable from anywhere on your system, point an entry-point at the ``launch_instance`` method in the extension's ``setup.py``: + +.. code-block:: python + + from setuptools import setup + + + setup( + name='myfrontend', + ... + entry_points={ + 'console_scripts': [ + 'jupyter-myextension = myextension:launch_instance' + ] + } + ) + +Distributing a server extension +=============================== + + + +Example Server Extension +======================== + +You can check some simple example on the `GitHub jupyter_server repository +`_. diff --git a/docs/source/developers/dependency.rst b/docs/source/developers/dependency.rst index d69aa28d7d..cb33e3330e 100644 --- a/docs/source/developers/dependency.rst +++ b/docs/source/developers/dependency.rst @@ -18,4 +18,4 @@ To pin your jupyter_server install to a specific version: .. code-block:: console - > pip install jupyter_server==1.0 \ No newline at end of file + > pip install jupyter_server==1.0 diff --git a/docs/source/developers/index.rst b/docs/source/developers/index.rst index 1266911dff..30be2e9f09 100644 --- a/docs/source/developers/index.rst +++ b/docs/source/developers/index.rst @@ -10,6 +10,5 @@ These pages target people writing Jupyter Web applications and server extensions dependency basic-extension - configurable-extension rest-api ../other/changelog \ No newline at end of file diff --git a/docs/source/extending/contents.rst b/docs/source/extending/contents.rst deleted file mode 100644 index cd0f7f7aaf..0000000000 --- a/docs/source/extending/contents.rst +++ /dev/null @@ -1,269 +0,0 @@ -.. _contents_api: - -Contents API -============ - -.. currentmodule:: jupyter_server.services.contents - -The Jupyter Notebook web application provides a graphical interface for -creating, opening, renaming, and deleting files in a virtual filesystem. - -The :class:`~manager.ContentsManager` class defines an abstract -API for translating these interactions into operations on a particular storage -medium. The default implementation, -:class:`~filemanager.FileContentsManager`, uses the local -filesystem of the server for storage and straightforwardly serializes notebooks -into JSON. Users can override these behaviors by supplying custom subclasses -of ContentsManager. - -This section describes the interface implemented by ContentsManager subclasses. -We refer to this interface as the **Contents API**. - -Data Model ----------- - -.. currentmodule:: jupyter_server.services.contents.manager - -Filesystem Entities -~~~~~~~~~~~~~~~~~~~ -.. _notebook models: - -ContentsManager methods represent virtual filesystem entities as dictionaries, -which we refer to as **models**. - -Models may contain the following entries: - -+--------------------+-----------+------------------------------+ -| Key | Type |Info | -+====================+===========+==============================+ -|**name** |unicode |Basename of the entity. | -+--------------------+-----------+------------------------------+ -|**path** |unicode |Full | -| | |(:ref:`API-style`) | -| | |path to the entity. | -+--------------------+-----------+------------------------------+ -|**type** |unicode |The entity type. One of | -| | |``"notebook"``, ``"file"`` or | -| | |``"directory"``. | -+--------------------+-----------+------------------------------+ -|**created** |datetime |Creation date of the entity. | -+--------------------+-----------+------------------------------+ -|**last_modified** |datetime |Last modified date of the | -| | |entity. | -+--------------------+-----------+------------------------------+ -|**content** |variable |The "content" of the entity. | -| | |(:ref:`See | -| | |Below`) | -+--------------------+-----------+------------------------------+ -|**mimetype** |unicode or |The mimetype of ``content``, | -| |``None`` |if any. (:ref:`See | -| | |Below`) | -+--------------------+-----------+------------------------------+ -|**format** |unicode or |The format of ``content``, | -| |``None`` |if any. (:ref:`See | -| | |Below`) | -+--------------------+-----------+------------------------------+ - -.. _modelcontent: - -Certain model fields vary in structure depending on the ``type`` field of the -model. There are three model types: **notebook**, **file**, and **directory**. - -- ``notebook`` models - - The ``format`` field is always ``"json"``. - - The ``mimetype`` field is always ``None``. - - The ``content`` field contains a - :class:`nbformat.notebooknode.NotebookNode` representing the .ipynb file - represented by the model. See the `NBFormat`_ documentation for a full - description. - -- ``file`` models - - The ``format`` field is either ``"text"`` or ``"base64"``. - - The ``mimetype`` field is ``text/plain`` for text-format models and - ``application/octet-stream`` for base64-format models. - - The ``content`` field is always of type ``unicode``. For text-format - file models, ``content`` simply contains the file's bytes after decoding - as UTF-8. Non-text (``base64``) files are read as bytes, base64 encoded, - and then decoded as UTF-8. - -- ``directory`` models - - The ``format`` field is always ``"json"``. - - The ``mimetype`` field is always ``None``. - - The ``content`` field contains a list of :ref:`content-free` - models representing the entities in the directory. - -.. note:: - - .. _contentfree: - - In certain circumstances, we don't need the full content of an entity to - complete a Contents API request. In such cases, we omit the ``mimetype``, - ``content``, and ``format`` keys from the model. This most commonly occurs - when listing a directory, in which circumstance we represent files within - the directory as content-less models to avoid having to recursively traverse - and serialize the entire filesystem. - -**Sample Models** - -.. code-block:: python - - # Notebook Model with Content - { - 'content': { - 'metadata': {}, - 'nbformat': 4, - 'nbformat_minor': 0, - 'cells': [ - { - 'cell_type': 'markdown', - 'metadata': {}, - 'source': 'Some **Markdown**', - }, - ], - }, - 'created': datetime(2015, 7, 25, 19, 50, 19, 19865), - 'format': 'json', - 'last_modified': datetime(2015, 7, 25, 19, 50, 19, 19865), - 'mimetype': None, - 'name': 'a.ipynb', - 'path': 'foo/a.ipynb', - 'type': 'notebook', - 'writable': True, - } - - # Notebook Model without Content - { - 'content': None, - 'created': datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), - 'format': None, - 'last_modified': datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), - 'mimetype': None, - 'name': 'a.ipynb', - 'path': 'foo/a.ipynb', - 'type': 'notebook', - 'writable': True - } - - -API Paths -~~~~~~~~~ -.. _apipaths: - -ContentsManager methods represent the locations of filesystem resources as -**API-style paths**. Such paths are interpreted as relative to the root -directory of the notebook server. For compatibility across systems, the -following guarantees are made: - -* Paths are always ``unicode``, not ``bytes``. -* Paths are not URL-escaped. -* Paths are always forward-slash (/) delimited, even on Windows. -* Leading and trailing slashes are stripped. For example, ``/foo/bar/buzz/`` - becomes ``foo/bar/buzz``. -* The empty string (``""``) represents the root directory. - - -Writing a Custom ContentsManager --------------------------------- - -The default ContentsManager is designed for users running the notebook as an -application on a personal computer. It stores notebooks as .ipynb files on the -local filesystem, and it maps files and directories in the Notebook UI to files -and directories on disk. It is possible to override how notebooks are stored -by implementing your own custom subclass of ``ContentsManager``. For example, -if you deploy the notebook in a context where you don't trust or don't have -access to the filesystem of the notebook server, it's possible to write your -own ContentsManager that stores notebooks and files in a database. - - -Required Methods -~~~~~~~~~~~~~~~~ - -A minimal complete implementation of a custom -:class:`~manager.ContentsManager` must implement the following -methods: - -.. autosummary:: - ContentsManager.get - ContentsManager.save - ContentsManager.delete_file - ContentsManager.rename_file - ContentsManager.file_exists - ContentsManager.dir_exists - ContentsManager.is_hidden - -You may be required to specify a Checkpoints object, as the default one, -``FileCheckpoints``, could be incompatible with your custom -ContentsManager. - -Customizing Checkpoints ------------------------ -.. currentmodule:: jupyter_server.services.contents.checkpoints - -Customized Checkpoint definitions allows behavior to be -altered and extended. - -The ``Checkpoints`` and ``GenericCheckpointsMixin`` classes -(from :mod:`jupyter_server.services.contents.checkpoints`) -have reusable code and are intended to be used together, -but require the following methods to be implemented. - -.. autosummary:: - Checkpoints.rename_checkpoint - Checkpoints.list_checkpoints - Checkpoints.delete_checkpoint - GenericCheckpointsMixin.create_file_checkpoint - GenericCheckpointsMixin.create_notebook_checkpoint - GenericCheckpointsMixin.get_file_checkpoint - GenericCheckpointsMixin.get_notebook_checkpoint - -No-op example -~~~~~~~~~~~~~ - -Here is an example of a no-op checkpoints object - note the mixin -comes first. The docstrings indicate what each method should do or -return for a more complete implementation. - -.. code-block:: python - - class NoOpCheckpoints(GenericCheckpointsMixin, Checkpoints): - """requires the following methods:""" - def create_file_checkpoint(self, content, format, path): - """ -> checkpoint model""" - def create_notebook_checkpoint(self, nb, path): - """ -> checkpoint model""" - def get_file_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" - def get_notebook_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'notebook', 'content': }""" - def delete_checkpoint(self, checkpoint_id, path): - """deletes a checkpoint for a file""" - def list_checkpoints(self, path): - """returns a list of checkpoint models for a given file, - default just does one per file - """ - return [] - def rename_checkpoint(self, checkpoint_id, old_path, new_path): - """renames checkpoint from old path to new path""" - -See ``GenericFileCheckpoints`` in :mod:`notebook.services.contents.filecheckpoints` -for a more complete example. - -Testing -------- -.. currentmodule:: jupyter_server.services.contents.tests - -:mod:`jupyter_server.services.contents.tests` includes several test suites written -against the abstract Contents API. This means that an excellent way to test a -new ContentsManager subclass is to subclass our tests to make them use your -ContentsManager. - -.. note:: - - PGContents_ is an example of a complete implementation of a custom - ``ContentsManager``. It stores notebooks and files in PostgreSQL_ and encodes - directories as SQL relations. PGContents also provides an example of how to - re-use the notebook's tests. - -.. _NBFormat: https://nbformat.readthedocs.io/en/latest/index.html -.. _PGContents: https://github.com/quantopian/pgcontents -.. _PostgreSQL: https://www.postgresql.org/ diff --git a/docs/source/extending/extensions.rst b/docs/source/extending/extensions.rst deleted file mode 100644 index be4b24b1d1..0000000000 --- a/docs/source/extending/extensions.rst +++ /dev/null @@ -1,12 +0,0 @@ -Jupyter Server Extensions -========================= - -A Jupyter Server extension is typically a module or package that extends—i.e. adds extra request handlers to—Jupyter server. - -The most basic way to write a Jupyter Server extension - -.. code-block:: python - - # def _load_jupyter_server_extension(servera): - - diff --git a/docs/source/extending/handlers.rst b/docs/source/extending/handlers.rst deleted file mode 100644 index ed6e86697f..0000000000 --- a/docs/source/extending/handlers.rst +++ /dev/null @@ -1,127 +0,0 @@ -Custom request handlers -======================= - -The notebook webserver can be interacted with using a well `defined -RESTful -API `__. -You can define custom RESTful API handlers in addition to the ones -provided by the notebook. As described below, to define a custom handler -you need to first write a notebook server extension. Then, in the -extension, you can register the custom handler. - -Writing a notebook server extension ------------------------------------ - -The notebook webserver is written in Python, hence your server extension -should be written in Python too. Server extensions, like IPython -extensions, are Python modules that define a specially named load -function, ``load_jupyter_server_extension``. This function is called -when the extension is loaded. - -.. code:: python - - def load_jupyter_server_extension(nb_server_app): - """ - Called when the extension is loaded. - - Args: - nb_server_app (NotebookWebApplication): handle to the Notebook webserver instance. - """ - pass - -To get the notebook server to load your custom extension, you'll need to -add it to the list of extensions to be loaded. You can do this using the -config system. ``NotebookApp.jpserver_extensions`` is a config variable -which is a dictionary of strings, each a Python module to be imported, mapping -to ``True`` to enable or ``False`` to disable each extension. -Because this variable is notebook config, you can set it two different -ways, using config files or via the command line. - -For example, to get your extension to load via the command line add a -double dash before the variable name, and put the Python dictionary in -double quotes. If your package is "mypackage" and module is -"mymodule", this would look like -``jupyter notebook --NotebookApp.jpserver_extensions="{'mypackage.mymodule':True}"`` -. -Basically the string should be Python importable. - -Alternatively, you can have your extension loaded regardless of the -command line args by setting the variable in the Jupyter config file. -The default location of the Jupyter config file is -``~/.jupyter/jupyter_notebook_config.py`` (see :doc:`/config_overview`). Inside -the config file, you can use Python to set the variable. For example, -the following config does the same as the previous command line example. - -.. code:: python - - c = get_config() - c.NotebookApp.jpserver_extensions = { - 'mypackage.mymodule': True, - } - -Before continuing, it's a good idea to verify that your extension is -being loaded. Use a print statement to print something unique. Launch -the notebook server and you should see your statement printed to the -console. - -Registering custom handlers ---------------------------- - -Once you've defined a server extension, you can register custom handlers -because you have a handle to the Notebook server app instance -(``nb_server_app`` above). However, you first need to define your custom -handler. To declare a custom handler, inherit from -``notebook.base.handlers.IPythonHandler``. The example below[1] is a -Hello World handler: - -.. code:: python - - from notebook.base.handlers import IPythonHandler - - class HelloWorldHandler(IPythonHandler): - def get(self): - self.finish('Hello, world!') - -The Jupyter Notebook server use -`Tornado `__ as its web framework. -For more information on how to implement request handlers, refer to the -`Tornado documentation on the -matter `__. - -After defining the handler, you need to register the handler with the -Notebook server. See the following example: - -.. code:: python - - web_app = nb_server_app.web_app - host_pattern = '.*$' - route_pattern = url_path_join(web_app.settings['base_url'], '/hello') - web_app.add_handlers(host_pattern, [(route_pattern, HelloWorldHandler)]) - -Putting this together with the extension code, the example looks like the -following: - -.. code:: python - - from notebook.utils import url_path_join - from notebook.base.handlers import IPythonHandler - - class HelloWorldHandler(IPythonHandler): - def get(self): - self.finish('Hello, world!') - - def load_jupyter_server_extension(nb_server_app): - """ - Called when the extension is loaded. - - Args: - nb_server_app: handle to the Notebook webserver instance. - """ - web_app = nb_server_app.web_app - host_pattern = '.*$' - route_pattern = url_path_join(web_app.settings['base_url'], '/hello') - web_app.add_handlers(host_pattern, [(route_pattern, HelloWorldHandler)]) - -References: - -1. `Peter Parente's Mindtrove `__ diff --git a/docs/source/extending/index.rst b/docs/source/extending/index.rst deleted file mode 100644 index 552890d079..0000000000 --- a/docs/source/extending/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -====================== -Extending the Notebook -====================== - -Certain subsystems of the notebook server are designed to be extended or -overridden by users. These documents explain these systems, and show how to -override the notebook's defaults with your own custom behavior. - -.. toctree:: - :maxdepth: 2 - - contents - savehooks - handlers - bundler_extensions diff --git a/docs/source/extending/savehooks.rst b/docs/source/extending/savehooks.rst deleted file mode 100644 index 900649d036..0000000000 --- a/docs/source/extending/savehooks.rst +++ /dev/null @@ -1,84 +0,0 @@ -File save hooks -=============== - -You can configure functions that are run whenever a file is saved. There are -two hooks available: - -* ``ContentsManager.pre_save_hook`` runs on the API path and model with - content. This can be used for things like stripping output that people don't - like adding to VCS noise. -* ``FileContentsManager.post_save_hook`` runs on the filesystem path and model - without content. This could be used to commit changes after every save, for - instance. - -They are both called with keyword arguments:: - - pre_save_hook(model=model, path=path, contents_manager=cm) - post_save_hook(model=model, os_path=os_path, contents_manager=cm) - -Examples --------- - -These can both be added to :file:`jupyter_server_config.py`. - -A pre-save hook for stripping output:: - - def scrub_output_pre_save(model, **kwargs): - """scrub output before saving notebooks""" - # only run on notebooks - if model['type'] != 'notebook': - return - # only run on nbformat v4 - if model['content']['nbformat'] != 4: - return - - for cell in model['content']['cells']: - if cell['cell_type'] != 'code': - continue - cell['outputs'] = [] - cell['execution_count'] = None - - c.FileContentsManager.pre_save_hook = scrub_output_pre_save - -A post-save hook to make a script equivalent whenever the notebook is saved -(replacing the ``--script`` option in older versions of the notebook): - -.. code-block:: python - - import io - import os - from jupyter_server.utils import to_api_path - - _script_exporter = None - - def script_post_save(model, os_path, contents_manager, **kwargs): - """convert notebooks to Python script after save with nbconvert - - replaces `ipython notebook --script` - """ - from nbconvert.exporters.script import ScriptExporter - - if model['type'] != 'notebook': - return - - global _script_exporter - - if _script_exporter is None: - _script_exporter = ScriptExporter(parent=contents_manager) - - log = contents_manager.log - - base, ext = os.path.splitext(os_path) - py_fname = base + '.py' - script, resources = _script_exporter.from_filename(os_path) - script_fname = base + resources.get('output_extension', '.txt') - log.info("Saving script /%s", to_api_path(script_fname, contents_manager.root_dir)) - - with io.open(script_fname, 'w', encoding='utf-8') as f: - f.write(script) - - c.FileContentsManager.post_save_hook = script_post_save - - -This could be a simple call to ``jupyter nbconvert --to script``, but spawning -the subprocess every time is quite slow. diff --git a/docs/source/other/full-config.rst b/docs/source/other/full-config.rst index 201e49880b..6203b78f1a 100644 --- a/docs/source/other/full-config.rst +++ b/docs/source/other/full-config.rst @@ -897,7 +897,7 @@ FileContentsManager.root_dir : Unicode No description -NotebookNotary.algorithm : 'blake2b'|'md5'|'sha3_384'|'sha512'|'sha3_224'|'sha3_512'|'sha3_256'|'blake2s'|'sha256'|'sha384'|'sha1'|'sha224' +NotebookNotary.algorithm : 'sha3_384'|'blake2b'|'sha3_224'|'blake2s'|'sha224'|'sha3_256'|'md5'|'sha3_512'|'sha384'|'sha512'|'sha1'|'sha256' Default: ``'sha256'`` The hashing algorithm used to sign notebooks.