Skip to content

Commit

Permalink
Merge pull request ipython#1934 from minrk/cellmd
Browse files Browse the repository at this point in the history
Cell/Worksheet metadata

* metadata dicts are attached to cells and worksheets
* restores collapsed flag to the nbformat - this change happened in the refactor, and was undocumented, and possibly accidental.  But we should either document it or fix it, and this includes a fix.
* adds a new field, `nbformat_minor`, used to denote minor bumps of the notebook format that expose new capabilities but don't prevent loading by older clients.
* Add a warning in Javascript if loading a multiworksheet notebook (which will exist in the future) as current JS code will only save the first.


closes ipython#1915
  • Loading branch information
fperez committed Jun 18, 2012
2 parents aad1285 + 5cc38bd commit e0e3a2d
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 29 deletions.
7 changes: 7 additions & 0 deletions IPython/frontend/html/notebook/static/js/cell.js
Expand Up @@ -19,6 +19,7 @@ var IPython = (function (IPython) {
this.read_only = false;
this.selected = false;
this.element = null;
this.metadata = {};
this.create_element();
if (this.element !== null) {
this.element.data("cell", this);
Expand Down Expand Up @@ -90,10 +91,16 @@ var IPython = (function (IPython) {


Cell.prototype.toJSON = function () {
var data = {};
data.metadata = this.metadata;
return data;
};


Cell.prototype.fromJSON = function (data) {
if (data.metadata !== undefined) {
this.metadata = data.metadata;
}
};


Expand Down
13 changes: 9 additions & 4 deletions IPython/frontend/html/notebook/static/js/codecell.js
Expand Up @@ -22,6 +22,7 @@ var IPython = (function (IPython) {
this.code_mirror = null;
this.input_prompt_number = null;
this.tooltip_on_tab = true;
this.collapsed = false;
IPython.Cell.apply(this, arguments);
};

Expand Down Expand Up @@ -200,17 +201,20 @@ var IPython = (function (IPython) {


CodeCell.prototype.collapse = function () {
this.output_area.collapse();
this.collapsed = true;
this.output_area.collapse();
};


CodeCell.prototype.expand = function () {
this.output_area.expand();
this.collapsed = false;
this.output_area.expand();
};


CodeCell.prototype.toggle_output = function () {
this.output_area.toggle_output();
this.collapsed = Boolean(1 - this.collapsed);
this.output_area.toggle_output();
};


Expand Down Expand Up @@ -264,6 +268,7 @@ var IPython = (function (IPython) {
// JSON serialization

CodeCell.prototype.fromJSON = function (data) {
IPython.Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === 'code') {
if (data.input !== undefined) {
this.set_text(data.input);
Expand All @@ -289,7 +294,7 @@ var IPython = (function (IPython) {


CodeCell.prototype.toJSON = function () {
var data = {};
var data = IPython.Cell.prototype.toJSON.apply(this);
data.input = this.get_text();
data.cell_type = 'code';
if (this.input_prompt_number) {
Expand Down
58 changes: 57 additions & 1 deletion IPython/frontend/html/notebook/static/js/notebook.js
Expand Up @@ -25,11 +25,14 @@ var IPython = (function (IPython) {
this.paste_enabled = false;
this.dirty = false;
this.metadata = {};
// single worksheet for now
this.worksheet_metadata = {};
this.control_key_active = false;
this.notebook_id = null;
this.notebook_name = null;
this.notebook_name_blacklist_re = /[\/\\:]/;
this.nbformat = 3 // Increment this when changing the nbformat
this.nbformat_minor = 0 // Increment this when changing the nbformat
this.style();
this.create_elements();
this.bind_events();
Expand Down Expand Up @@ -1018,6 +1021,9 @@ var IPython = (function (IPython) {
// Only handle 1 worksheet for now.
var worksheet = data.worksheets[0];
if (worksheet !== undefined) {
if (worksheet.metadata) {
this.worksheet_metadata = worksheet.metadata;
}
var new_cells = worksheet.cells;
ncells = new_cells.length;
var cell_data = null;
Expand All @@ -1034,6 +1040,27 @@ var IPython = (function (IPython) {
new_cell.fromJSON(cell_data);
};
};
if (data.worksheets.length > 1) {
var dialog = $('<div/>');
dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
"but this version of IPython can only handle the first. " +
"If you save this notebook, worksheets after the first will be lost."
);
this.element.append(dialog);
dialog.dialog({
resizable: false,
modal: true,
title: "Multiple worksheets",
closeText: "",
close: function(event, ui) {$(this).dialog('destroy').remove();},
buttons : {
"OK": function () {
$(this).dialog('close');
}
},
width: 400
});
}
};


Expand All @@ -1046,7 +1073,10 @@ var IPython = (function (IPython) {
};
var data = {
// Only handle 1 worksheet for now.
worksheets : [{cells:cell_array}],
worksheets : [{
cells: cell_array,
metadata: this.worksheet_metadata
}],
metadata : this.metadata
};
return data;
Expand All @@ -1057,6 +1087,7 @@ var IPython = (function (IPython) {
var data = this.toJSON();
data.metadata.name = this.notebook_name;
data.nbformat = this.nbformat;
data.nbformat_minor = this.nbformat_minor;
// We do the call with settings so we can set cache to false.
var settings = {
processData : false,
Expand Down Expand Up @@ -1133,6 +1164,31 @@ var IPython = (function (IPython) {
},
width: 400
});
} else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
var that = this;
var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
this_vs + ". You can still work with this notebook, but some features " +
"introduced in later notebook versions may not be available."

var dialog = $('<div/>');
dialog.html(msg);
this.element.append(dialog);
dialog.dialog({
resizable: false,
modal: true,
title: "Newer Notebook",
closeText: "",
close: function(event, ui) {$(this).dialog('destroy').remove();},
buttons : {
"OK": function () {
$(this).dialog('close');
}
},
width: 400
});

}
// Create the kernel after the notebook is completely loaded to prevent
// code execution upon loading, which is a security risk.
Expand Down
3 changes: 2 additions & 1 deletion IPython/frontend/html/notebook/static/js/textcell.js
Expand Up @@ -155,6 +155,7 @@ var IPython = (function (IPython) {


TextCell.prototype.fromJSON = function (data) {
IPython.Cell.prototype.fromJSON.apply(this, arguments);
if (data.cell_type === this.cell_type) {
if (data.source !== undefined) {
this.set_text(data.source);
Expand All @@ -170,7 +171,7 @@ var IPython = (function (IPython) {


TextCell.prototype.toJSON = function () {
var data = {};
var data = IPython.Cell.prototype.toJSON.apply(this);
data.cell_type = this.cell_type;
data.source = this.get_text();
return data;
Expand Down
30 changes: 20 additions & 10 deletions IPython/nbformat/current.py
Expand Up @@ -28,14 +28,17 @@
from IPython.nbformat.v3 import (
NotebookNode,
new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
parse_filename, new_metadata, new_author, new_heading_cell, nbformat
parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
nbformat_minor,
)

#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------

current_nbformat = nbformat
current_nbformat_minor = nbformat_minor



class NBFormatError(Exception):
Expand All @@ -45,24 +48,30 @@ class NBFormatError(Exception):
def parse_json(s, **kwargs):
"""Parse a string into a (nbformat, dict) tuple."""
d = json.loads(s, **kwargs)
nbf = d.get('nbformat',1)
return nbf, d
nbf = d.get('nbformat', 1)
nbm = d.get('nbformat_minor', 0)
return nbf, nbm, d


def parse_py(s, **kwargs):
"""Parse a string into a (nbformat, string) tuple."""
pattern = r'# <nbformat>(?P<nbformat>\d+)</nbformat>'
nbf = current_nbformat
nbm = current_nbformat_minor

pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
m = re.search(pattern,s)
if m is not None:
nbf = int(m.group('nbformat'))
else:
nbf = current_nbformat
return nbf, s
digits = m.group('nbformat').split('.')
nbf = int(digits[0])
if len(digits) > 1:
nbm = int(digits[1])

return nbf, nbm, s


def reads_json(s, **kwargs):
"""Read a JSON notebook from a string and return the NotebookNode object."""
nbf, d = parse_json(s, **kwargs)
nbf, minor, d = parse_json(s, **kwargs)
if nbf == 1:
nb = v1.to_notebook_json(d, **kwargs)
nb = v3.convert_to_this_nbformat(nb, orig_version=1)
Expand All @@ -71,6 +80,7 @@ def reads_json(s, **kwargs):
nb = v3.convert_to_this_nbformat(nb, orig_version=2)
elif nbf == 3:
nb = v3.to_notebook_json(d, **kwargs)
nb = v3.convert_to_this_nbformat(nb, orig_version=3, orig_minor=minor)
else:
raise NBFormatError('Unsupported JSON nbformat version: %i' % nbf)
return nb
Expand All @@ -82,7 +92,7 @@ def writes_json(nb, **kwargs):

def reads_py(s, **kwargs):
"""Read a .py notebook from a string and return the NotebookNode object."""
nbf, s = parse_py(s, **kwargs)
nbf, nbm, s = parse_py(s, **kwargs)
if nbf == 2:
nb = v2.to_notebook_py(s, **kwargs)
elif nbf == 3:
Expand Down
2 changes: 1 addition & 1 deletion IPython/nbformat/v3/__init__.py
Expand Up @@ -19,7 +19,7 @@
from .nbbase import (
NotebookNode,
new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
new_metadata, new_author, new_heading_cell, nbformat
new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor
)

from .nbjson import reads as reads_json, writes as writes_json
Expand Down
14 changes: 11 additions & 3 deletions IPython/nbformat/v3/convert.py
Expand Up @@ -17,7 +17,8 @@
#-----------------------------------------------------------------------------

from .nbbase import (
new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
nbformat, nbformat_minor
)

from IPython.nbformat import v2
Expand All @@ -26,7 +27,7 @@
# Code
#-----------------------------------------------------------------------------

def convert_to_this_nbformat(nb, orig_version=2):
def convert_to_this_nbformat(nb, orig_version=2, orig_minor=0):
"""Convert a notebook to the v3 format.
Parameters
Expand All @@ -35,16 +36,23 @@ def convert_to_this_nbformat(nb, orig_version=2):
The Python representation of the notebook to convert.
orig_version : int
The original version of the notebook to convert.
orig_minor : int
The original minor version of the notebook to convert (only relevant for v >= 3).
"""
if orig_version == 1:
nb = v2.convert_to_this_nbformat(nb)
orig_version = 2
if orig_version == 2:
# Mark the original nbformat so consumers know it has been converted.
nb.nbformat = 3
nb.nbformat = nbformat
nb.nbformat_minor = nbformat_minor

nb.orig_nbformat = 2
return nb
elif orig_version == 3:
if orig_minor != nbformat_minor:
nb.orig_nbformat_minor = orig_minor
nb.nbformat_minor = nbformat_minor
return nb
else:
raise ValueError('Cannot convert a notebook from v%s to v3' % orig_version)
Expand Down
14 changes: 10 additions & 4 deletions IPython/nbformat/v3/nbbase.py
Expand Up @@ -32,6 +32,7 @@

# Change this when incrementing the nbformat version
nbformat = 3
nbformat_minor = 0

class NotebookNode(Struct):
pass
Expand Down Expand Up @@ -92,7 +93,7 @@ def new_output(output_type=None, output_text=None, output_png=None,


def new_code_cell(input=None, prompt_number=None, outputs=None,
language=u'python', collapsed=False):
language=u'python', collapsed=False, metadata=None):
"""Create a new code cell with input and output"""
cell = NotebookNode()
cell.cell_type = u'code'
Expand All @@ -108,10 +109,11 @@ def new_code_cell(input=None, prompt_number=None, outputs=None,
cell.outputs = outputs
if collapsed is not None:
cell.collapsed = bool(collapsed)
cell.metadata = NotebookNode(metadata or {})

return cell

def new_text_cell(cell_type, source=None, rendered=None):
def new_text_cell(cell_type, source=None, rendered=None, metadata=None):
"""Create a new text cell."""
cell = NotebookNode()
# VERSIONHACK: plaintext -> raw
Expand All @@ -122,11 +124,12 @@ def new_text_cell(cell_type, source=None, rendered=None):
cell.source = unicode(source)
if rendered is not None:
cell.rendered = unicode(rendered)
cell.metadata = NotebookNode(metadata or {})
cell.cell_type = cell_type
return cell


def new_heading_cell(source=None, rendered=None, level=1):
def new_heading_cell(source=None, rendered=None, level=1, metadata=None):
"""Create a new section cell with a given integer level."""
cell = NotebookNode()
cell.cell_type = u'heading'
Expand All @@ -135,10 +138,11 @@ def new_heading_cell(source=None, rendered=None, level=1):
if rendered is not None:
cell.rendered = unicode(rendered)
cell.level = int(level)
cell.metadata = NotebookNode(metadata or {})
return cell


def new_worksheet(name=None, cells=None):
def new_worksheet(name=None, cells=None, metadata=None):
"""Create a worksheet by name with with a list of cells."""
ws = NotebookNode()
if name is not None:
Expand All @@ -147,13 +151,15 @@ def new_worksheet(name=None, cells=None):
ws.cells = []
else:
ws.cells = list(cells)
ws.metadata = NotebookNode(metadata or {})
return ws


def new_notebook(name=None, metadata=None, worksheets=None):
"""Create a notebook by name, id and a list of worksheets."""
nb = NotebookNode()
nb.nbformat = nbformat
nb.nbformat_minor = nbformat_minor
if worksheets is None:
nb.worksheets = []
else:
Expand Down

0 comments on commit e0e3a2d

Please sign in to comment.