Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
0
venv/Lib/site-packages/notebook/bundler/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/bundler/__init__.py
Normal file
7
venv/Lib/site-packages/notebook/bundler/__main__.py
Normal file
7
venv/Lib/site-packages/notebook/bundler/__main__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from .bundlerextensions import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
307
venv/Lib/site-packages/notebook/bundler/bundlerextensions.py
Normal file
307
venv/Lib/site-packages/notebook/bundler/bundlerextensions.py
Normal file
|
@ -0,0 +1,307 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import sys
|
||||
import os
|
||||
|
||||
from ..extensions import BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED
|
||||
from .._version import __version__
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
|
||||
from jupyter_core.paths import jupyter_config_path
|
||||
|
||||
from traitlets.utils.importstring import import_item
|
||||
from traitlets import Bool
|
||||
|
||||
BUNDLER_SECTION = "notebook"
|
||||
BUNDLER_SUBSECTION = "bundlerextensions"
|
||||
|
||||
def _get_bundler_metadata(module):
|
||||
"""Gets the list of bundlers associated with a Python package.
|
||||
|
||||
Returns a tuple of (the module, [{
|
||||
'name': 'unique name of the bundler',
|
||||
'label': 'file menu item label for the bundler',
|
||||
'module_name': 'dotted package/module name containing the bundler',
|
||||
'group': 'download or deploy parent menu item'
|
||||
}])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
"""
|
||||
m = import_item(module)
|
||||
if not hasattr(m, '_jupyter_bundlerextension_paths'):
|
||||
raise KeyError('The Python module {} does not contain a valid bundlerextension'.format(module))
|
||||
bundlers = m._jupyter_bundlerextension_paths()
|
||||
return m, bundlers
|
||||
|
||||
def _set_bundler_state(name, label, module_name, group, state,
|
||||
user=True, sys_prefix=False, logger=None):
|
||||
"""Set whether a bundler is enabled or disabled.
|
||||
|
||||
Returns True if the final state is the one requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Unique name of the bundler
|
||||
label : string
|
||||
Human-readable label for the bundler menu item in the notebook UI
|
||||
module_name : string
|
||||
Dotted module/package name containing the bundler
|
||||
group : string
|
||||
'download' or 'deploy' indicating the parent menu containing the label
|
||||
state : bool
|
||||
The state in which to leave the extension
|
||||
user : bool [default: True]
|
||||
Whether to update the user's .jupyter/nbconfig directory
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to update the sys.prefix, i.e. environment. Will override
|
||||
`user`.
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
user = False if sys_prefix else user
|
||||
config_dir = os.path.join(
|
||||
_get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
|
||||
if logger:
|
||||
logger.info("{} {} bundler {}...".format(
|
||||
"Enabling" if state else "Disabling",
|
||||
name,
|
||||
module_name
|
||||
))
|
||||
|
||||
if state:
|
||||
cm.update(BUNDLER_SECTION, {
|
||||
BUNDLER_SUBSECTION: {
|
||||
name: {
|
||||
"label": label,
|
||||
"module_name": module_name,
|
||||
"group" : group
|
||||
}
|
||||
}
|
||||
})
|
||||
else:
|
||||
cm.update(BUNDLER_SECTION, {
|
||||
BUNDLER_SUBSECTION: {
|
||||
name: None
|
||||
}
|
||||
})
|
||||
|
||||
return (cm.get(BUNDLER_SECTION)
|
||||
.get(BUNDLER_SUBSECTION, {})
|
||||
.get(name) is not None) == state
|
||||
|
||||
|
||||
def _set_bundler_state_python(state, module, user, sys_prefix, logger=None):
|
||||
"""Enables or disables bundlers defined in a Python package.
|
||||
|
||||
Returns a list of whether the state was achieved for each bundler.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
state : Bool
|
||||
Whether the extensions should be enabled
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool
|
||||
Enable/disable in the sys.prefix, i.e. environment
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
m, bundlers = _get_bundler_metadata(module)
|
||||
return [_set_bundler_state(name=bundler["name"],
|
||||
label=bundler["label"],
|
||||
module_name=bundler["module_name"],
|
||||
group=bundler["group"],
|
||||
state=state,
|
||||
user=user, sys_prefix=sys_prefix,
|
||||
logger=logger)
|
||||
for bundler in bundlers]
|
||||
|
||||
def enable_bundler_python(module, user=True, sys_prefix=False, logger=None):
|
||||
"""Enables bundlers defined in a Python package.
|
||||
|
||||
Returns whether each bundle defined in the packaged was enabled or not.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool [default: True]
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to enable in the sys.prefix, i.e. environment. Will override
|
||||
`user`
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
return _set_bundler_state_python(True, module, user, sys_prefix,
|
||||
logger=logger)
|
||||
|
||||
def disable_bundler_python(module, user=True, sys_prefix=False, logger=None):
|
||||
"""Disables bundlers defined in a Python package.
|
||||
|
||||
Returns whether each bundle defined in the packaged was enabled or not.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool [default: True]
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to enable in the sys.prefix, i.e. environment. Will override
|
||||
`user`
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
return _set_bundler_state_python(False, module, user, sys_prefix,
|
||||
logger=logger)
|
||||
|
||||
class ToggleBundlerExtensionApp(BaseExtensionApp):
|
||||
"""A base class for apps that enable/disable bundlerextensions"""
|
||||
name = "jupyter bundlerextension enable/disable"
|
||||
version = __version__
|
||||
description = "Enable/disable a bundlerextension in configuration."
|
||||
|
||||
user = Bool(True, config=True, help="Apply the configuration only for the current user (default)")
|
||||
|
||||
_toggle_value = None
|
||||
|
||||
def _config_file_name_default(self):
|
||||
"""The default config file name."""
|
||||
return 'jupyter_notebook_config'
|
||||
|
||||
def toggle_bundler_python(self, module):
|
||||
"""Toggle some extensions in an importable Python module.
|
||||
|
||||
Returns a list of booleans indicating whether the state was changed as
|
||||
requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
"""
|
||||
toggle = (enable_bundler_python if self._toggle_value
|
||||
else disable_bundler_python)
|
||||
return toggle(module,
|
||||
user=self.user,
|
||||
sys_prefix=self.sys_prefix,
|
||||
logger=self.log)
|
||||
|
||||
def start(self):
|
||||
if not self.extra_args:
|
||||
sys.exit('Please specify an bundlerextension/package to enable or disable')
|
||||
elif len(self.extra_args) > 1:
|
||||
sys.exit('Please specify one bundlerextension/package at a time')
|
||||
if self.python:
|
||||
self.toggle_bundler_python(self.extra_args[0])
|
||||
else:
|
||||
raise NotImplementedError('Cannot install bundlers from non-Python packages')
|
||||
|
||||
class EnableBundlerExtensionApp(ToggleBundlerExtensionApp):
|
||||
"""An App that enables bundlerextensions"""
|
||||
name = "jupyter bundlerextension enable"
|
||||
description = """
|
||||
Enable a bundlerextension in frontend configuration.
|
||||
|
||||
Usage
|
||||
jupyter bundlerextension enable [--system|--sys-prefix]
|
||||
"""
|
||||
_toggle_value = True
|
||||
|
||||
class DisableBundlerExtensionApp(ToggleBundlerExtensionApp):
|
||||
"""An App that disables bundlerextensions"""
|
||||
name = "jupyter bundlerextension disable"
|
||||
description = """
|
||||
Disable a bundlerextension in frontend configuration.
|
||||
|
||||
Usage
|
||||
jupyter bundlerextension disable [--system|--sys-prefix]
|
||||
"""
|
||||
_toggle_value = None
|
||||
|
||||
|
||||
class ListBundlerExtensionApp(BaseExtensionApp):
|
||||
"""An App that lists and validates nbextensions"""
|
||||
name = "jupyter nbextension list"
|
||||
version = __version__
|
||||
description = "List all nbextensions known by the configuration system"
|
||||
|
||||
def list_nbextensions(self):
|
||||
"""List all the nbextensions"""
|
||||
config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
|
||||
|
||||
print("Known bundlerextensions:")
|
||||
|
||||
for config_dir in config_dirs:
|
||||
head = u' config dir: {}'.format(config_dir)
|
||||
head_shown = False
|
||||
|
||||
cm = BaseJSONConfigManager(parent=self, config_dir=config_dir)
|
||||
data = cm.get('notebook')
|
||||
if 'bundlerextensions' in data:
|
||||
if not head_shown:
|
||||
# only show heading if there is an nbextension here
|
||||
print(head)
|
||||
head_shown = True
|
||||
|
||||
for bundler_id, info in data['bundlerextensions'].items():
|
||||
label = info.get('label')
|
||||
module = info.get('module_name')
|
||||
if label is None or module is None:
|
||||
msg = u' {} {}'.format(bundler_id, RED_DISABLED)
|
||||
else:
|
||||
msg = u' "{}" from {} {}'.format(
|
||||
label, module, GREEN_ENABLED
|
||||
)
|
||||
print(msg)
|
||||
|
||||
def start(self):
|
||||
"""Perform the App's functions as configured"""
|
||||
self.list_nbextensions()
|
||||
|
||||
|
||||
class BundlerExtensionApp(BaseExtensionApp):
|
||||
"""Base jupyter bundlerextension command entry point"""
|
||||
name = "jupyter bundlerextension"
|
||||
version = __version__
|
||||
description = "Work with Jupyter bundler extensions"
|
||||
examples = """
|
||||
jupyter bundlerextension list # list all configured bundlers
|
||||
jupyter bundlerextension enable --py <packagename> # enable all bundlers in a Python package
|
||||
jupyter bundlerextension disable --py <packagename> # disable all bundlers in a Python package
|
||||
"""
|
||||
|
||||
subcommands = dict(
|
||||
enable=(EnableBundlerExtensionApp, "Enable a bundler extension"),
|
||||
disable=(DisableBundlerExtensionApp, "Disable a bundler extension"),
|
||||
list=(ListBundlerExtensionApp, "List bundler extensions")
|
||||
)
|
||||
|
||||
def start(self):
|
||||
"""Perform the App's functions as configured"""
|
||||
super(BundlerExtensionApp, self).start()
|
||||
|
||||
# The above should have called a subcommand and raised NoStart; if we
|
||||
# get here, it didn't, so we should self.log.info a message.
|
||||
subcmds = ", ".join(sorted(self.subcommands))
|
||||
sys.exit("Please supply at least one subcommand: %s" % subcmds)
|
||||
|
||||
main = BundlerExtensionApp.launch_instance
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
88
venv/Lib/site-packages/notebook/bundler/handlers.py
Normal file
88
venv/Lib/site-packages/notebook/bundler/handlers.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
"""Tornado handler for bundling notebooks."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from ipython_genutils.importstring import import_item
|
||||
from tornado import web, gen
|
||||
|
||||
from notebook.utils import maybe_future, url2path
|
||||
from notebook.base.handlers import IPythonHandler
|
||||
from notebook.services.config import ConfigManager
|
||||
|
||||
from . import tools
|
||||
|
||||
|
||||
class BundlerHandler(IPythonHandler):
|
||||
def initialize(self):
|
||||
"""Make tools module available on the handler instance for compatibility
|
||||
with existing bundler API and ease of reference."""
|
||||
self.tools = tools
|
||||
|
||||
def get_bundler(self, bundler_id):
|
||||
"""
|
||||
Get bundler metadata from config given a bundler ID.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bundler_id: str
|
||||
Unique bundler ID within the notebook/bundlerextensions config section
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Bundler metadata with label, group, and module_name attributes
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError
|
||||
If the bundler ID is unknown
|
||||
"""
|
||||
cm = ConfigManager()
|
||||
return cm.get('notebook').get('bundlerextensions', {})[bundler_id]
|
||||
|
||||
@web.authenticated
|
||||
@gen.coroutine
|
||||
def get(self, path):
|
||||
"""Bundle the given notebook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path: str
|
||||
Path to the notebook (path parameter)
|
||||
bundler: str
|
||||
Bundler ID to use (query parameter)
|
||||
"""
|
||||
bundler_id = self.get_query_argument('bundler')
|
||||
model = self.contents_manager.get(path=url2path(path))
|
||||
|
||||
try:
|
||||
bundler = self.get_bundler(bundler_id)
|
||||
except KeyError as e:
|
||||
raise web.HTTPError(400, 'Bundler %s not enabled' %
|
||||
bundler_id) from e
|
||||
|
||||
module_name = bundler['module_name']
|
||||
try:
|
||||
# no-op in python3, decode error in python2
|
||||
module_name = str(module_name)
|
||||
except UnicodeEncodeError:
|
||||
# Encode unicode as utf-8 in python2 else import_item fails
|
||||
module_name = module_name.encode('utf-8')
|
||||
|
||||
try:
|
||||
bundler_mod = import_item(module_name)
|
||||
except ImportError as e:
|
||||
raise web.HTTPError(500, 'Could not import bundler %s ' %
|
||||
bundler_id) from e
|
||||
|
||||
# Let the bundler respond in any way it sees fit and assume it will
|
||||
# finish the request
|
||||
yield maybe_future(bundler_mod.bundle(self, model))
|
||||
|
||||
_bundler_id_regex = r'(?P<bundler_id>[A-Za-z0-9_]+)'
|
||||
|
||||
default_handlers = [
|
||||
(r"/bundle/(.*)", BundlerHandler)
|
||||
]
|
47
venv/Lib/site-packages/notebook/bundler/tarball_bundler.py
Normal file
47
venv/Lib/site-packages/notebook/bundler/tarball_bundler.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import io
|
||||
import tarfile
|
||||
import nbformat
|
||||
|
||||
def _jupyter_bundlerextension_paths():
|
||||
"""Metadata for notebook bundlerextension"""
|
||||
return [{
|
||||
# unique bundler name
|
||||
"name": "tarball_bundler",
|
||||
# module containing bundle function
|
||||
"module_name": "notebook.bundler.tarball_bundler",
|
||||
# human-readable menu item label
|
||||
"label" : "Notebook Tarball (tar.gz)",
|
||||
# group under 'deploy' or 'download' menu
|
||||
"group" : "download",
|
||||
}]
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Create a compressed tarball containing the notebook document.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
handler : tornado.web.RequestHandler
|
||||
Handler that serviced the bundle request
|
||||
model : dict
|
||||
Notebook model from the configured ContentManager
|
||||
"""
|
||||
notebook_filename = model['name']
|
||||
notebook_content = nbformat.writes(model['content']).encode('utf-8')
|
||||
notebook_name = os.path.splitext(notebook_filename)[0]
|
||||
tar_filename = '{}.tar.gz'.format(notebook_name)
|
||||
|
||||
info = tarfile.TarInfo(notebook_filename)
|
||||
info.size = len(notebook_content)
|
||||
|
||||
with io.BytesIO() as tar_buffer:
|
||||
with tarfile.open(tar_filename, "w:gz", fileobj=tar_buffer) as tar:
|
||||
tar.addfile(info, io.BytesIO(notebook_content))
|
||||
|
||||
handler.set_attachment_header(tar_filename)
|
||||
handler.set_header('Content-Type', 'application/gzip')
|
||||
|
||||
# Return the buffer value as the response
|
||||
handler.finish(tar_buffer.getvalue())
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
Used to test globbing.
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"nbformat_minor": 0,
|
||||
"cells": [],
|
||||
"nbformat": 4,
|
||||
"metadata": {}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Used to test globbing.
|
|
@ -0,0 +1,80 @@
|
|||
"""Test the bundlers API."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import io
|
||||
from os.path import join as pjoin
|
||||
|
||||
from notebook.tests.launchnotebook import NotebookTestBase
|
||||
from nbformat import write
|
||||
from nbformat.v4 import (
|
||||
new_notebook, new_markdown_cell, new_code_cell, new_output,
|
||||
)
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Bundler test stub. Echo the notebook path."""
|
||||
handler.finish(model['path'])
|
||||
|
||||
class BundleAPITest(NotebookTestBase):
|
||||
"""Test the bundlers web service API"""
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"""Make a test notebook. Borrowed from nbconvert test. Assumes the class
|
||||
teardown will clean it up in the end."""
|
||||
super(BundleAPITest, cls).setup_class()
|
||||
nbdir = cls.notebook_dir
|
||||
|
||||
nb = new_notebook()
|
||||
|
||||
nb.cells.append(new_markdown_cell(u'Created by test'))
|
||||
cc1 = new_code_cell(source=u'print(2*6)')
|
||||
cc1.outputs.append(new_output(output_type="stream", text=u'12'))
|
||||
nb.cells.append(cc1)
|
||||
|
||||
with io.open(pjoin(nbdir, 'testnb.ipynb'), 'w',
|
||||
encoding='utf-8') as f:
|
||||
write(nb, f, version=4)
|
||||
|
||||
def test_missing_bundler_arg(self):
|
||||
"""Should respond with 400 error about missing bundler arg"""
|
||||
resp = self.request('GET', 'bundle/fake.ipynb')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn('Missing argument bundler', resp.text)
|
||||
|
||||
def test_notebook_not_found(self):
|
||||
"""Shoudl respond with 404 error about missing notebook"""
|
||||
resp = self.request('GET', 'bundle/fake.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertIn('Not Found', resp.text)
|
||||
|
||||
def test_bundler_not_enabled(self):
|
||||
"""Should respond with 400 error about disabled bundler"""
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn('Bundler fake_bundler not enabled', resp.text)
|
||||
|
||||
def test_bundler_import_error(self):
|
||||
"""Should respond with 500 error about failure to load bundler module"""
|
||||
with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock:
|
||||
mock.return_value = {'module_name': 'fake_module'}
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
mock.assert_called_with('fake_bundler')
|
||||
self.assertEqual(resp.status_code, 500)
|
||||
self.assertIn('Could not import bundler fake_bundler', resp.text)
|
||||
|
||||
def test_bundler_invoke(self):
|
||||
"""Should respond with 200 and output from test bundler stub"""
|
||||
with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock:
|
||||
mock.return_value = {'module_name': 'notebook.bundler.tests.test_bundler_api'}
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'stub_bundler'})
|
||||
mock.assert_called_with('stub_bundler')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertIn('testnb.ipynb', resp.text)
|
|
@ -0,0 +1,124 @@
|
|||
"""Test the bundler tools."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import notebook.bundler.tools as tools
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
class TestBundlerTools(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
def test_get_no_cell_references(self):
|
||||
'''Should find no references in a regular HTML comment.'''
|
||||
no_references = tools.get_cell_reference_patterns({'source':'''!<--
|
||||
a
|
||||
b
|
||||
c
|
||||
-->''', 'cell_type':'markdown'})
|
||||
self.assertEqual(len(no_references), 0)
|
||||
|
||||
def test_get_cell_reference_patterns_comment_multiline(self):
|
||||
'''Should find two references and ignore a comment within an HTML comment.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''<!--associate:
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
-->'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references, str(references))
|
||||
self.assertEqual(len(references), 2, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_comment_trailing_filename(self):
|
||||
'''Should find three references within an HTML comment.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''<!--associate:c
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
-->'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references and 'c' in references, str(references))
|
||||
self.assertEqual(len(references), 3, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_precode(self):
|
||||
'''Should find no references in a fenced code block in a *code* cell.'''
|
||||
self.assertTrue(tools.get_cell_reference_patterns)
|
||||
no_references = tools.get_cell_reference_patterns({'source':'''```
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
```
|
||||
''', 'cell_type':'code'})
|
||||
self.assertEqual(len(no_references), 0)
|
||||
|
||||
def test_get_cell_reference_patterns_precode_mdcomment(self):
|
||||
'''Should find two references and ignore a comment in a fenced code block.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''```
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
```'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references, str(references))
|
||||
self.assertEqual(len(references), 2, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_precode_backticks(self):
|
||||
'''Should find three references in a fenced code block.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''```c
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
```'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references and 'c' in references, str(references))
|
||||
self.assertEqual(len(references), 3, str(references))
|
||||
|
||||
def test_glob_dir(self):
|
||||
'''Should expand to single file in the resources/ subfolder.'''
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'),
|
||||
tools.expand_references(HERE, ['resources/empty.ipynb']))
|
||||
|
||||
def test_glob_subdir(self):
|
||||
'''Should expand to all files in the resources/ subfolder.'''
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'),
|
||||
tools.expand_references(HERE, ['resources/']))
|
||||
|
||||
def test_glob_splat(self):
|
||||
'''Should expand to all contents under this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['*'])
|
||||
self.assertIn('test_bundler_tools.py', globs, globs)
|
||||
self.assertIn('resources', globs, globs)
|
||||
|
||||
def test_glob_splatsplat_in_middle(self):
|
||||
'''Should expand to test_file.txt deep under this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['resources/**/test_file.txt'])
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_glob_splatsplat_trailing(self):
|
||||
'''Should expand to all descendants of this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['resources/**'])
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'), globs, globs)
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_glob_splatsplat_leading(self):
|
||||
'''Should expand to test_file.txt under any path.'''
|
||||
globs = tools.expand_references(HERE, ['**/test_file.txt'])
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
self.assertIn(os.path.join('resources', 'another_subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_copy_filelist(self):
|
||||
'''Should copy select files from source to destination'''
|
||||
globs = tools.expand_references(HERE, ['**/test_file.txt'])
|
||||
tools.copy_filelist(HERE, self.tmp, globs)
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.tmp, 'resources', 'subdir', 'test_file.txt')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.tmp, 'resources', 'another_subdir', 'test_file.txt')))
|
||||
self.assertFalse(os.path.isfile(os.path.join(self.tmp, 'resources', 'empty.ipynb')))
|
|
@ -0,0 +1,72 @@
|
|||
"""Test the bundlerextension CLI."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from unittest.mock import patch
|
||||
from ipython_genutils.tempdir import TemporaryDirectory
|
||||
from ipython_genutils import py3compat
|
||||
|
||||
from traitlets.tests.utils import check_help_all_output
|
||||
|
||||
import notebook.nbextensions as nbextensions
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
from ..bundlerextensions import (_get_config_dir, enable_bundler_python,
|
||||
disable_bundler_python)
|
||||
|
||||
def test_help_output():
|
||||
check_help_all_output('notebook.bundler.bundlerextensions')
|
||||
check_help_all_output('notebook.bundler.bundlerextensions', ['enable'])
|
||||
check_help_all_output('notebook.bundler.bundlerextensions', ['disable'])
|
||||
|
||||
class TestBundlerExtensionCLI(unittest.TestCase):
|
||||
"""Tests the bundlerextension CLI against the example zip_bundler."""
|
||||
def setUp(self):
|
||||
"""Build an isolated config environment."""
|
||||
td = TemporaryDirectory()
|
||||
|
||||
self.test_dir = py3compat.cast_unicode(td.name)
|
||||
self.data_dir = os.path.join(self.test_dir, 'data')
|
||||
self.config_dir = os.path.join(self.test_dir, 'config')
|
||||
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
|
||||
self.system_path = [self.system_data_dir]
|
||||
|
||||
# Use temp directory, not real user or system config paths
|
||||
self.patch_env = patch.dict('os.environ', {
|
||||
'JUPYTER_CONFIG_DIR': self.config_dir,
|
||||
'JUPYTER_DATA_DIR': self.data_dir,
|
||||
})
|
||||
self.patch_env.start()
|
||||
self.patch_system_path = patch.object(nbextensions,
|
||||
'SYSTEM_JUPYTER_PATH', self.system_path)
|
||||
self.patch_system_path.start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Remove the test config environment."""
|
||||
shutil.rmtree(self.test_dir, ignore_errors=True)
|
||||
self.patch_env.stop()
|
||||
self.patch_system_path.stop()
|
||||
|
||||
def test_enable(self):
|
||||
"""Should add the bundler to the notebook configuration."""
|
||||
enable_bundler_python('notebook.bundler.zip_bundler')
|
||||
|
||||
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
bundlers = cm.get('notebook').get('bundlerextensions', {})
|
||||
self.assertEqual(len(bundlers), 1)
|
||||
self.assertIn('notebook_zip_download', bundlers)
|
||||
|
||||
def test_disable(self):
|
||||
"""Should remove the bundler from the notebook configuration."""
|
||||
self.test_enable()
|
||||
disable_bundler_python('notebook.bundler.zip_bundler')
|
||||
|
||||
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
bundlers = cm.get('notebook').get('bundlerextensions', {})
|
||||
self.assertEqual(len(bundlers), 0)
|
230
venv/Lib/site-packages/notebook/bundler/tools.py
Normal file
230
venv/Lib/site-packages/notebook/bundler/tools.py
Normal file
|
@ -0,0 +1,230 @@
|
|||
"""Set of common tools to aid bundler implementations."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import shutil
|
||||
import errno
|
||||
import nbformat
|
||||
import fnmatch
|
||||
import glob
|
||||
|
||||
def get_file_references(abs_nb_path, version):
|
||||
"""Gets a list of files referenced either in Markdown fenced code blocks
|
||||
or in HTML comments from the notebook. Expands patterns expressed in
|
||||
gitignore syntax (https://git-scm.com/docs/gitignore). Returns the
|
||||
fully expanded list of filenames relative to the notebook dirname.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
abs_nb_path: str
|
||||
Absolute path of the notebook on disk
|
||||
version: int
|
||||
Version of the notebook document format to use
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Filename strings relative to the notebook path
|
||||
"""
|
||||
ref_patterns = get_reference_patterns(abs_nb_path, version)
|
||||
expanded = expand_references(os.path.dirname(abs_nb_path), ref_patterns)
|
||||
return expanded
|
||||
|
||||
def get_reference_patterns(abs_nb_path, version):
|
||||
"""Gets a list of reference patterns either in Markdown fenced code blocks
|
||||
or in HTML comments from the notebook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
abs_nb_path: str
|
||||
Absolute path of the notebook on disk
|
||||
version: int
|
||||
Version of the notebook document format to use
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Pattern strings from the notebook
|
||||
"""
|
||||
notebook = nbformat.read(abs_nb_path, version)
|
||||
referenced_list = []
|
||||
for cell in notebook.cells:
|
||||
references = get_cell_reference_patterns(cell)
|
||||
if references:
|
||||
referenced_list = referenced_list + references
|
||||
return referenced_list
|
||||
|
||||
def get_cell_reference_patterns(cell):
|
||||
'''
|
||||
Retrieves the list of references from a single notebook cell. Looks for
|
||||
fenced code blocks or HTML comments in Markdown cells, e.g.,
|
||||
|
||||
```
|
||||
some.csv
|
||||
foo/
|
||||
!foo/bar
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
<!--associate:
|
||||
some.csv
|
||||
foo/
|
||||
!foo/bar
|
||||
-->
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cell: dict
|
||||
Notebook cell object
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Reference patterns found in the cell
|
||||
'''
|
||||
referenced = []
|
||||
# invisible after execution: unrendered HTML comment
|
||||
if cell.get('cell_type').startswith('markdown') and cell.get('source').startswith('<!--associate:'):
|
||||
lines = cell.get('source')[len('<!--associate:'):].splitlines()
|
||||
for line in lines:
|
||||
if line.startswith('-->'):
|
||||
break
|
||||
# Trying to go out of the current directory leads to
|
||||
# trouble when deploying
|
||||
if line.find('../') < 0 and not line.startswith('#'):
|
||||
referenced.append(line)
|
||||
# visible after execution: rendered as a code element within a pre element
|
||||
elif cell.get('cell_type').startswith('markdown') and cell.get('source').find('```') >= 0:
|
||||
source = cell.get('source')
|
||||
offset = source.find('```')
|
||||
lines = source[offset + len('```'):].splitlines()
|
||||
for line in lines:
|
||||
if line.startswith('```'):
|
||||
break
|
||||
# Trying to go out of the current directory leads to
|
||||
# trouble when deploying
|
||||
if line.find('../') < 0 and not line.startswith('#'):
|
||||
referenced.append(line)
|
||||
|
||||
# Clean out blank references
|
||||
return [ref for ref in referenced if ref.strip()]
|
||||
|
||||
def expand_references(root_path, references):
|
||||
"""Expands a set of reference patterns by evaluating them against the
|
||||
given root directory. Expansions are performed against patterns
|
||||
expressed in the same manner as in gitignore
|
||||
(https://git-scm.com/docs/gitignore).
|
||||
|
||||
NOTE: Temporarily changes the current working directory when called.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
root_path: str
|
||||
Assumed root directory for the patterns
|
||||
references: list
|
||||
Reference patterns from get_reference_patterns expressed with
|
||||
forward-slash directory separators
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Filename strings relative to the root path
|
||||
"""
|
||||
# Use normpath to convert to platform specific slashes, but be sure
|
||||
# to retain a trailing slash which normpath pulls off
|
||||
normalized_references = []
|
||||
for ref in references:
|
||||
normalized_ref = os.path.normpath(ref)
|
||||
# un-normalized separator
|
||||
if ref.endswith('/'):
|
||||
normalized_ref += os.sep
|
||||
normalized_references.append(normalized_ref)
|
||||
references = normalized_references
|
||||
|
||||
globbed = []
|
||||
negations = []
|
||||
must_walk = []
|
||||
for pattern in references:
|
||||
if pattern and pattern.find(os.sep) < 0:
|
||||
# simple shell glob
|
||||
cwd = os.getcwd()
|
||||
os.chdir(root_path)
|
||||
if pattern.startswith('!'):
|
||||
negations = negations + glob.glob(pattern[1:])
|
||||
else:
|
||||
globbed = globbed + glob.glob(pattern)
|
||||
os.chdir(cwd)
|
||||
elif pattern:
|
||||
must_walk.append(pattern)
|
||||
|
||||
for pattern in must_walk:
|
||||
pattern_is_negation = pattern.startswith('!')
|
||||
if pattern_is_negation:
|
||||
testpattern = pattern[1:]
|
||||
else:
|
||||
testpattern = pattern
|
||||
for root, _, filenames in os.walk(root_path):
|
||||
for filename in filenames:
|
||||
joined = os.path.join(root[len(root_path) + 1:], filename)
|
||||
if testpattern.endswith(os.sep):
|
||||
if joined.startswith(testpattern):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
elif testpattern.find('**') >= 0:
|
||||
# path wildcard
|
||||
ends = testpattern.split('**')
|
||||
if len(ends) == 2:
|
||||
if joined.startswith(ends[0]) and joined.endswith(ends[1]):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
else:
|
||||
# segments should be respected
|
||||
if fnmatch.fnmatch(joined, testpattern):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
|
||||
for negated in negations:
|
||||
try:
|
||||
globbed.remove(negated)
|
||||
except ValueError as err:
|
||||
pass
|
||||
return set(globbed)
|
||||
|
||||
def copy_filelist(src, dst, src_relative_filenames):
|
||||
"""Copies the given list of files, relative to src, into dst, creating
|
||||
directories along the way as needed and ignore existence errors.
|
||||
Skips any files that do not exist. Does not create empty directories
|
||||
from src in dst.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src: str
|
||||
Root of the source directory
|
||||
dst: str
|
||||
Root of the destination directory
|
||||
src_relative_filenames: list
|
||||
Filenames relative to src
|
||||
"""
|
||||
for filename in src_relative_filenames:
|
||||
# Only consider the file if it exists in src
|
||||
if os.path.isfile(os.path.join(src, filename)):
|
||||
parent_relative = os.path.dirname(filename)
|
||||
if parent_relative:
|
||||
# Make sure the parent directory exists
|
||||
parent_dst = os.path.join(dst, parent_relative)
|
||||
try:
|
||||
os.makedirs(parent_dst)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST:
|
||||
pass
|
||||
else:
|
||||
raise exc
|
||||
shutil.copy2(os.path.join(src, filename), os.path.join(dst, filename))
|
59
venv/Lib/site-packages/notebook/bundler/zip_bundler.py
Normal file
59
venv/Lib/site-packages/notebook/bundler/zip_bundler.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import io
|
||||
import zipfile
|
||||
import notebook.bundler.tools as tools
|
||||
|
||||
def _jupyter_bundlerextension_paths():
|
||||
"""Metadata for notebook bundlerextension"""
|
||||
return [{
|
||||
'name': 'notebook_zip_download',
|
||||
'label': 'IPython Notebook bundle (.zip)',
|
||||
'module_name': 'notebook.bundler.zip_bundler',
|
||||
'group': 'download'
|
||||
}]
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Create a zip file containing the original notebook and files referenced
|
||||
from it. Retain the referenced files in paths relative to the notebook.
|
||||
Return the zip as a file download.
|
||||
|
||||
Assumes the notebook and other files are all on local disk.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
handler : tornado.web.RequestHandler
|
||||
Handler that serviced the bundle request
|
||||
model : dict
|
||||
Notebook model from the configured ContentManager
|
||||
"""
|
||||
abs_nb_path = os.path.join(handler.settings['contents_manager'].root_dir,
|
||||
model['path'])
|
||||
notebook_filename = model['name']
|
||||
notebook_name = os.path.splitext(notebook_filename)[0]
|
||||
|
||||
# Headers
|
||||
zip_filename = os.path.splitext(notebook_name)[0] + '.zip'
|
||||
handler.set_attachment_header(zip_filename)
|
||||
handler.set_header('Content-Type', 'application/zip')
|
||||
|
||||
# Get associated files
|
||||
ref_filenames = tools.get_file_references(abs_nb_path, 4)
|
||||
|
||||
# Prepare the zip file
|
||||
zip_buffer = io.BytesIO()
|
||||
zipf = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
|
||||
zipf.write(abs_nb_path, notebook_filename)
|
||||
|
||||
notebook_dir = os.path.dirname(abs_nb_path)
|
||||
for nb_relative_filename in ref_filenames:
|
||||
# Build absolute path to file on disk
|
||||
abs_fn = os.path.join(notebook_dir, nb_relative_filename)
|
||||
# Store file under path relative to notebook
|
||||
zipf.write(abs_fn, nb_relative_filename)
|
||||
|
||||
zipf.close()
|
||||
|
||||
# Return the buffer value as the response
|
||||
handler.finish(zip_buffer.getvalue())
|
Loading…
Add table
Add a link
Reference in a new issue