convert.py
208 lines
| 6.8 KiB
| text/x-python
|
PythonLexer
|
r18573 | """Code for converting notebooks to and from v3.""" | ||
|
r18568 | |||
|
r18573 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
|
r18568 | |||
|
r18577 | import json | ||
|
r18568 | from .nbbase import ( | ||
|
r18573 | nbformat, nbformat_minor, | ||
|
r18577 | NotebookNode, | ||
|
r18568 | ) | ||
|
r18573 | from IPython.nbformat import v3 | ||
|
r18578 | from IPython.utils.log import get_logger | ||
|
r18568 | |||
|
r18573 | def upgrade(nb, from_version=3, from_minor=0): | ||
"""Convert a notebook to v4. | ||||
|
r18568 | |||
Parameters | ||||
---------- | ||||
nb : NotebookNode | ||||
The Python representation of the notebook to convert. | ||||
from_version : int | ||||
The original version of the notebook to convert. | ||||
from_minor : int | ||||
The original minor version of the notebook to convert (only relevant for v >= 3). | ||||
""" | ||||
|
r18578 | from IPython.nbformat.current import validate, ValidationError | ||
|
r18573 | if from_version == 3: | ||
|
r18577 | # Validate the notebook before conversion | ||
|
r18578 | try: | ||
validate(nb, version=from_version) | ||||
except ValidationError as e: | ||||
get_logger().error("Notebook JSON is not valid v%i: %s", from_version, e) | ||||
|
r18577 | |||
|
r18568 | # Mark the original nbformat so consumers know it has been converted. | ||
|
r18578 | nb.pop('orig_nbformat', None) | ||
|
r18577 | nb.metadata.orig_nbformat = 3 | ||
# Mark the new format | ||||
|
r18568 | nb.nbformat = nbformat | ||
nb.nbformat_minor = nbformat_minor | ||||
|
r18573 | # remove worksheet(s) | ||
nb['cells'] = cells = [] | ||||
# In the unlikely event of multiple worksheets, | ||||
# they will be flattened | ||||
for ws in nb.pop('worksheets', []): | ||||
# upgrade each cell | ||||
for cell in ws['cells']: | ||||
cells.append(upgrade_cell(cell)) | ||||
|
r18577 | # upgrade metadata | ||
|
r18573 | nb.metadata.pop('name', '') | ||
|
r18577 | # Validate the converted notebook before returning it | ||
|
r18578 | try: | ||
validate(nb, version=nbformat) | ||||
except ValidationError as e: | ||||
get_logger().error("Notebook JSON is not valid v%i: %s", nbformat, e) | ||||
|
r18568 | return nb | ||
|
r18573 | elif from_version == 4: | ||
# nothing to do | ||||
|
r18568 | if from_minor != nbformat_minor: | ||
|
r18577 | nb.metadata.orig_nbformat_minor = from_minor | ||
|
r18568 | nb.nbformat_minor = nbformat_minor | ||
|
r18577 | |||
|
r18568 | return nb | ||
else: | ||||
|
r18573 | raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \ | ||
|
r18568 | 'Try using the IPython.nbformat.convert module.' % from_version) | ||
|
r18573 | def upgrade_cell(cell): | ||
"""upgrade a cell from v3 to v4 | ||||
|
r18568 | |||
|
r18573 | code cell: | ||
- remove language metadata | ||||
- cell.input -> cell.source | ||||
- update outputs | ||||
""" | ||||
|
r18577 | cell.setdefault('metadata', NotebookNode()) | ||
|
r18573 | if cell.cell_type == 'code': | ||
|
r18577 | cell.pop('language', '') | ||
cell.metadata.collapsed = cell.pop('collapsed') | ||||
|
r18573 | cell.source = cell.pop('input') | ||
|
r18577 | cell.setdefault('prompt_number', None) | ||
cell.outputs = upgrade_outputs(cell.outputs) | ||||
elif cell.cell_type == 'html': | ||||
# Technically, this exists. It will never happen in practice. | ||||
cell.cell_type = 'markdown' | ||||
|
r18573 | return cell | ||
def downgrade_cell(cell): | ||||
if cell.cell_type == 'code': | ||||
|
r18577 | cell.language = 'python' | ||
cell.input = cell.pop('source', '') | ||||
cell.collapsed = cell.metadata.pop('collapsed', False) | ||||
cell.outputs = downgrade_outputs(cell.outputs) | ||||
|
r18573 | return cell | ||
_mime_map = { | ||||
"text" : "text/plain", | ||||
"html" : "text/html", | ||||
"svg" : "image/svg+xml", | ||||
"png" : "image/png", | ||||
"jpeg" : "image/jpeg", | ||||
"latex" : "text/latex", | ||||
"json" : "application/json", | ||||
"javascript" : "application/javascript", | ||||
}; | ||||
def to_mime_key(d): | ||||
"""convert dict with v3 aliases to plain mime-type keys""" | ||||
for alias, mime in _mime_map.items(): | ||||
if alias in d: | ||||
d[mime] = d.pop(alias) | ||||
return d | ||||
def from_mime_key(d): | ||||
"""convert dict with mime-type keys to v3 aliases""" | ||||
for alias, mime in _mime_map.items(): | ||||
if mime in d: | ||||
d[alias] = d.pop(mime) | ||||
return d | ||||
def upgrade_output(output): | ||||
"""upgrade a single code cell output from v3 to v4 | ||||
- pyout -> execute_result | ||||
- pyerr -> error | ||||
- mime-type keys | ||||
|
r18577 | - stream.stream -> stream.name | ||
|
r18573 | """ | ||
|
r18577 | output.setdefault('metadata', NotebookNode()) | ||
if output['output_type'] in {'pyout', 'display_data'}: | ||||
if output['output_type'] == 'pyout': | ||||
output['output_type'] = 'execute_result' | ||||
|
r18573 | to_mime_key(output) | ||
|
r18577 | to_mime_key(output.metadata) | ||
if 'application/json' in output: | ||||
output['application/json'] = json.loads(output['application/json']) | ||||
# promote ascii bytes (from v2) to unicode | ||||
for key in ('image/png', 'image/jpeg'): | ||||
if key in output and isinstance(output[key], bytes): | ||||
output[key] = output[key].decode('ascii') | ||||
|
r18573 | elif output['output_type'] == 'pyerr': | ||
output['output_type'] = 'error' | ||||
|
r18577 | elif output['output_type'] == 'stream': | ||
output['name'] = output.pop('stream') | ||||
|
r18573 | return output | ||
def downgrade_output(output): | ||||
"""downgrade a single code cell output to v3 from v4 | ||||
- pyout <- execute_result | ||||
- pyerr <- error | ||||
- un-mime-type keys | ||||
|
r18577 | - stream.stream <- stream.name | ||
|
r18573 | """ | ||
if output['output_type'] == 'execute_result': | ||||
output['output_type'] = 'pyout' | ||||
|
r18577 | if 'application/json' in output: | ||
output['application/json'] = json.dumps(output['application/json']) | ||||
|
r18573 | from_mime_key(output) | ||
from_mime_key(output.get('metadata', {})) | ||||
elif output['output_type'] == 'error': | ||||
output['output_type'] = 'pyerr' | ||||
elif output['output_type'] == 'display_data': | ||||
|
r18577 | if 'application/json' in output: | ||
output['application/json'] = json.dumps(output['application/json']) | ||||
|
r18573 | from_mime_key(output) | ||
from_mime_key(output.get('metadata', {})) | ||||
|
r18577 | elif output['output_type'] == 'stream': | ||
output['stream'] = output.pop('name') | ||||
output.pop('metadata') | ||||
|
r18573 | return output | ||
def upgrade_outputs(outputs): | ||||
"""upgrade outputs of a code cell from v3 to v4""" | ||||
return [upgrade_output(op) for op in outputs] | ||||
def downgrade_outputs(outputs): | ||||
"""downgrade outputs of a code cell to v3 from v4""" | ||||
return [downgrade_output(op) for op in outputs] | ||||
|
r18568 | |||
def downgrade(nb): | ||||
|
r18573 | """Convert a v4 notebook to v3. | ||
|
r18568 | |||
Parameters | ||||
---------- | ||||
nb : NotebookNode | ||||
The Python representation of the notebook to convert. | ||||
""" | ||||
|
r18577 | from IPython.nbformat.current import validate | ||
# Validate the notebook before conversion | ||||
validate(nb, version=nbformat) | ||||
|
r18573 | if nb.nbformat != 4: | ||
|
r18568 | return nb | ||
|
r18573 | nb.nbformat = v3.nbformat | ||
nb.nbformat_minor = v3.nbformat_minor | ||||
|
r18577 | cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ] | ||
|
r18573 | nb.worksheets = [v3.new_worksheet(cells=cells)] | ||
nb.metadata.setdefault('name', '') | ||||
|
r18577 | nb.metadata.pop('orig_nbformat', None) | ||
nb.metadata.pop('orig_nbformat_minor', None) | ||||
# Validate the converted notebook before returning it | ||||
validate(nb, version=v3.nbformat) | ||||
return nb | ||||