exporter.py
282 lines
| 9.8 KiB
| text/x-python
|
PythonLexer
MinRK
|
r13664 | """This module defines a base Exporter class. For Jinja template-based export, | ||
see templateexporter.py. | ||||
Jonathan Frederic
|
r12502 | """ | ||
from __future__ import print_function, absolute_import | ||||
import io | ||||
import os | ||||
import copy | ||||
import collections | ||||
import datetime | ||||
from IPython.config.configurable import LoggingConfigurable | ||||
from IPython.config import Config | ||||
MinRK
|
r18605 | from IPython import nbformat | ||
Jessica B. Hamrick
|
r19469 | from IPython.utils.traitlets import MetaHasTraits, Unicode, List, TraitError | ||
Jonathan Frederic
|
r12502 | from IPython.utils.importstring import import_item | ||
Jonathan Frederic
|
r12505 | from IPython.utils import text, py3compat | ||
Jonathan Frederic
|
r12502 | |||
class ResourcesDict(collections.defaultdict): | ||||
def __missing__(self, key): | ||||
return '' | ||||
Jessica B. Hamrick
|
r19469 | class FilenameExtension(Unicode): | ||
Jessica B. Hamrick
|
r19460 | """A trait for filename extensions.""" | ||
default_value = u'' | ||||
info_text = 'a filename extension, beginning with a dot' | ||||
def validate(self, obj, value): | ||||
Jessica B. Hamrick
|
r19469 | # cast to proper unicode | ||
value = super(FilenameExtension, self).validate(obj, value) | ||||
Jessica B. Hamrick
|
r19460 | |||
Jessica B. Hamrick
|
r19469 | # check that it starts with a dot | ||
Jessica B. Hamrick
|
r19471 | if value and not value.startswith('.'): | ||
Jessica B. Hamrick
|
r19464 | msg = "FileExtension trait '{}' does not begin with a dot: {!r}" | ||
Jessica B. Hamrick
|
r19460 | raise TraitError(msg.format(self.name, value)) | ||
return value | ||||
Jonathan Frederic
|
r12505 | class Exporter(LoggingConfigurable): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12518 | Class containing methods that sequentially run a list of preprocessors on a | ||
NotebookNode object and then return the modified NotebookNode object and | ||||
accompanying resources dict. | ||||
Jonathan Frederic
|
r12502 | """ | ||
Jessica B. Hamrick
|
r19462 | file_extension = FilenameExtension( | ||
Thomas Kluyver
|
r19027 | '.txt', config=True, | ||
Jonathan Frederic
|
r12502 | help="Extension of the file that should be written to disk" | ||
) | ||||
Thomas Kluyver
|
r13834 | # MIME type of the result file, for HTTP response headers. | ||
# This is *not* a traitlet, because we want to be able to access it from | ||||
# the class, not just on instances. | ||||
output_mimetype = '' | ||||
Thomas Kluyver
|
r13830 | |||
Jonathan Frederic
|
r12505 | #Configurability, allows the user to easily add filters and preprocessors. | ||
preprocessors = List(config=True, | ||||
help="""List of preprocessors, by name or namespace, to enable.""") | ||||
Jonathan Frederic
|
r12502 | |||
MinRK
|
r15774 | _preprocessors = List() | ||
Jonathan Frederic
|
r12502 | |||
Thomas Kluyver
|
r21318 | default_preprocessors = List([ | ||
'IPython.nbconvert.preprocessors.ExecutePreprocessor', | ||||
'IPython.nbconvert.preprocessors.ClearOutputPreprocessor', | ||||
'IPython.nbconvert.preprocessors.coalesce_streams', | ||||
Matthias BUSSONNIER
|
r12826 | 'IPython.nbconvert.preprocessors.SVG2PDFPreprocessor', | ||
'IPython.nbconvert.preprocessors.CSSHTMLHeaderPreprocessor', | ||||
'IPython.nbconvert.preprocessors.RevealHelpPreprocessor', | ||||
'IPython.nbconvert.preprocessors.LatexPreprocessor', | ||||
Thomas Kluyver
|
r21319 | 'IPython.nbconvert.preprocessors.HighlightMagicsPreprocessor', | ||
Thomas Kluyver
|
r21318 | 'IPython.nbconvert.preprocessors.ExtractOutputPreprocessor', | ||
], | ||||
Jonathan Frederic
|
r12502 | config=True, | ||
Jonathan Frederic
|
r12505 | help="""List of preprocessors available by default, by name, namespace, | ||
Jonathan Frederic
|
r12502 | instance, or type.""") | ||
def __init__(self, config=None, **kw): | ||||
""" | ||||
Public constructor | ||||
Parameters | ||||
---------- | ||||
config : config | ||||
User configuration instance. | ||||
""" | ||||
MinRK
|
r13472 | with_default_config = self.default_config | ||
if config: | ||||
with_default_config.merge(config) | ||||
super(Exporter, self).__init__(config=with_default_config, **kw) | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | self._init_preprocessors() | ||
Jonathan Frederic
|
r12502 | |||
@property | ||||
def default_config(self): | ||||
return Config() | ||||
Jonathan Frederic
|
r12518 | def from_notebook_node(self, nb, resources=None, **kw): | ||
Jonathan Frederic
|
r12502 | """ | ||
Convert a notebook from a notebook node instance. | ||||
Parameters | ||||
---------- | ||||
MinRK
|
r18606 | nb : :class:`~IPython.nbformat.NotebookNode` | ||
MinRK
|
r18605 | Notebook node (dict-like with attr-access) | ||
Thomas Kluyver
|
r13598 | resources : dict | ||
Additional resources that can be accessed read/write by | ||||
preprocessors and filters. | ||||
**kw | ||||
Ignored (?) | ||||
Jonathan Frederic
|
r12502 | """ | ||
nb_copy = copy.deepcopy(nb) | ||||
resources = self._init_resources(resources) | ||||
MinRK
|
r15672 | |||
if 'language' in nb['metadata']: | ||||
resources['language'] = nb['metadata']['language'].lower() | ||||
Jonathan Frederic
|
r12502 | |||
# Preprocess | ||||
Jonathan Frederic
|
r12518 | nb_copy, resources = self._preprocess(nb_copy, resources) | ||
Jonathan Frederic
|
r12502 | |||
return nb_copy, resources | ||||
def from_filename(self, filename, resources=None, **kw): | ||||
""" | ||||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
filename : str | ||||
Full filename of the notebook file to open and convert. | ||||
""" | ||||
Jonathan Frederic
|
r12505 | # Pull the metadata from the filesystem. | ||
Jonathan Frederic
|
r12502 | if resources is None: | ||
resources = ResourcesDict() | ||||
if not 'metadata' in resources or resources['metadata'] == '': | ||||
resources['metadata'] = ResourcesDict() | ||||
Jessica B. Hamrick
|
r20619 | path, basename = os.path.split(filename) | ||
Jonathan Frederic
|
r12502 | notebook_name = basename[:basename.rfind('.')] | ||
resources['metadata']['name'] = notebook_name | ||||
Jessica B. Hamrick
|
r20619 | resources['metadata']['path'] = path | ||
Jonathan Frederic
|
r12502 | |||
modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename)) | ||||
Jonathan Frederic
|
r12505 | resources['metadata']['modified_date'] = modified_date.strftime(text.date_format) | ||
Jonathan Frederic
|
r12502 | |||
Thomas Kluyver
|
r13656 | with io.open(filename, encoding='utf-8') as f: | ||
MinRK
|
r18605 | return self.from_notebook_node(nbformat.read(f, as_version=4), resources=resources, **kw) | ||
Jonathan Frederic
|
r12502 | |||
def from_file(self, file_stream, resources=None, **kw): | ||||
""" | ||||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
file_stream : file-like object | ||||
Notebook file-like object to convert. | ||||
""" | ||||
MinRK
|
r18605 | return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw) | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | def register_preprocessor(self, preprocessor, enabled=False): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | Register a preprocessor. | ||
Jonathan Frederic
|
r12518 | Preprocessors are classes that act upon the notebook before it is | ||
Jonathan Frederic
|
r12505 | passed into the Jinja templating engine. preprocessors are also | ||
Jonathan Frederic
|
r12502 | capable of passing additional information to the Jinja | ||
templating engine. | ||||
Parameters | ||||
---------- | ||||
Jonathan Frederic
|
r12505 | preprocessor : preprocessor | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | if preprocessor is None: | ||
raise TypeError('preprocessor') | ||||
isclass = isinstance(preprocessor, type) | ||||
Jonathan Frederic
|
r12502 | constructed = not isclass | ||
Jonathan Frederic
|
r12708 | # Handle preprocessor's registration based on it's type | ||
Jonathan Frederic
|
r12505 | if constructed and isinstance(preprocessor, py3compat.string_types): | ||
Jonathan Frederic
|
r12708 | # Preprocessor is a string, import the namespace and recursively call | ||
# this register_preprocessor method | ||||
Jonathan Frederic
|
r12505 | preprocessor_cls = import_item(preprocessor) | ||
return self.register_preprocessor(preprocessor_cls, enabled) | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | if constructed and hasattr(preprocessor, '__call__'): | ||
Jonathan Frederic
|
r12708 | # Preprocessor is a function, no need to construct it. | ||
# Register and return the preprocessor. | ||||
Jonathan Frederic
|
r12502 | if enabled: | ||
Jonathan Frederic
|
r12505 | preprocessor.enabled = True | ||
self._preprocessors.append(preprocessor) | ||||
return preprocessor | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | elif isclass and isinstance(preprocessor, MetaHasTraits): | ||
Jonathan Frederic
|
r12708 | # Preprocessor is configurable. Make sure to pass in new default for | ||
# the enabled flag if one was specified. | ||||
Jonathan Frederic
|
r12505 | self.register_preprocessor(preprocessor(parent=self), enabled) | ||
Jonathan Frederic
|
r12502 | |||
elif isclass: | ||||
Jonathan Frederic
|
r12708 | # Preprocessor is not configurable, construct it | ||
Jonathan Frederic
|
r12505 | self.register_preprocessor(preprocessor(), enabled) | ||
Jonathan Frederic
|
r12502 | |||
else: | ||||
Jonathan Frederic
|
r12708 | # Preprocessor is an instance of something without a __call__ | ||
# attribute. | ||||
Jonathan Frederic
|
r12505 | raise TypeError('preprocessor') | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | def _init_preprocessors(self): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | Register all of the preprocessors needed for this exporter, disabled | ||
Jonathan Frederic
|
r12502 | unless specified explicitly. | ||
""" | ||||
MinRK
|
r15774 | self._preprocessors = [] | ||
# Load default preprocessors (not necessarly enabled by default). | ||||
for preprocessor in self.default_preprocessors: | ||||
self.register_preprocessor(preprocessor) | ||||
# Load user-specified preprocessors. Enable by default. | ||||
for preprocessor in self.preprocessors: | ||||
self.register_preprocessor(preprocessor, enabled=True) | ||||
Jonathan Frederic
|
r12502 | |||
def _init_resources(self, resources): | ||||
#Make sure the resources dict is of ResourcesDict type. | ||||
if resources is None: | ||||
resources = ResourcesDict() | ||||
if not isinstance(resources, ResourcesDict): | ||||
new_resources = ResourcesDict() | ||||
new_resources.update(resources) | ||||
resources = new_resources | ||||
#Make sure the metadata extension exists in resources | ||||
if 'metadata' in resources: | ||||
if not isinstance(resources['metadata'], ResourcesDict): | ||||
Nicholas Bollweg
|
r19904 | new_metadata = ResourcesDict() | ||
new_metadata.update(resources['metadata']) | ||||
resources['metadata'] = new_metadata | ||||
Jonathan Frederic
|
r12502 | else: | ||
resources['metadata'] = ResourcesDict() | ||||
if not resources['metadata']['name']: | ||||
resources['metadata']['name'] = 'Notebook' | ||||
#Set the output extension | ||||
resources['output_extension'] = self.file_extension | ||||
return resources | ||||
Jonathan Frederic
|
r12518 | def _preprocess(self, nb, resources): | ||
Jonathan Frederic
|
r12502 | """ | ||
Preprocess the notebook before passing it into the Jinja engine. | ||||
To preprocess the notebook is to apply all of the | ||||
Parameters | ||||
---------- | ||||
nb : notebook node | ||||
notebook that is being exported. | ||||
resources : a dict of additional resources that | ||||
Jonathan Frederic
|
r12505 | can be accessed read/write by preprocessors | ||
Jonathan Frederic
|
r12502 | """ | ||
# Do a copy.deepcopy first, | ||||
Jonathan Frederic
|
r12505 | # we are never safe enough with what the preprocessors could do. | ||
Jonathan Frederic
|
r12502 | nbc = copy.deepcopy(nb) | ||
resc = copy.deepcopy(resources) | ||||
Jonathan Frederic
|
r12505 | #Run each preprocessor on the notebook. Carry the output along | ||
#to each preprocessor | ||||
for preprocessor in self._preprocessors: | ||||
nbc, resc = preprocessor(nbc, resc) | ||||
Jonathan Frederic
|
r12502 | return nbc, resc | ||