##// END OF EJS Templates
update nbformat.current to v4
MinRK -
Show More
@@ -0,0 +1,155 b''
1 {
2 "metadata": {
3 "cell_tags": ["<None>", null],
4 "name": "",
5 "kernel_info": {
6 "name": "python",
7 "language": "python"
8 }
9 },
10 "nbformat": 4,
11 "nbformat_minor": 0,
12 "cells": [
13 {
14 "cell_type": "heading",
15 "level": 1,
16 "metadata": {},
17 "source": [
18 "nbconvert latex test"
19 ]
20 },
21 {
22 "cell_type": "markdown",
23 "metadata": {},
24 "source": [
25 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
26 ]
27 },
28 {
29 "cell_type": "heading",
30 "level": 2,
31 "metadata": {},
32 "source": [
33 "Printed Using Python"
34 ]
35 },
36 {
37 "cell_type": "code",
38 "source": [
39 "print(\"hello\")"
40 ],
41 "metadata": {
42 "collapsed": false,
43 "autoscroll": false
44 },
45 "outputs": [
46 {
47 "output_type": "stream",
48 "metadata": {},
49 "name": "stdout",
50 "text": [
51 "hello\n"
52 ]
53 }
54 ],
55 "prompt_number": 1
56 },
57 {
58 "cell_type": "heading",
59 "level": 2,
60 "metadata": {},
61 "source": [
62 "Pyout"
63 ]
64 },
65 {
66 "cell_type": "code",
67 "source": [
68 "from IPython.display import HTML\n",
69 "HTML(\"\"\"\n",
70 "<script>\n",
71 "console.log(\"hello\");\n",
72 "</script>\n",
73 "<b>HTML</b>\n",
74 "\"\"\")"
75 ],
76 "metadata": {
77 "collapsed": false,
78 "autoscroll": false
79 },
80 "outputs": [
81 {
82 "text/html": [
83 "\n",
84 "<script>\n",
85 "console.log(\"hello\");\n",
86 "</script>\n",
87 "<b>HTML</b>\n"
88 ],
89 "metadata": {},
90 "output_type": "execute_result",
91 "prompt_number": 3,
92 "text/plain": [
93 "<IPython.core.display.HTML at 0x1112757d0>"
94 ]
95 }
96 ],
97 "prompt_number": 3
98 },
99 {
100 "cell_type": "code",
101 "source": [
102 "%%javascript\n",
103 "console.log(\"hi\");"
104 ],
105 "metadata": {
106 "collapsed": false,
107 "autoscroll": false
108 },
109 "outputs": [
110 {
111 "text/javascript": [
112 "console.log(\"hi\");"
113 ],
114 "metadata": {},
115 "output_type": "display_data",
116 "text/plain": [
117 "<IPython.core.display.Javascript at 0x1112b4b50>"
118 ]
119 }
120 ],
121 "prompt_number": 7
122 },
123 {
124 "cell_type": "heading",
125 "level": 3,
126 "metadata": {},
127 "source": [
128 "Image"
129 ]
130 },
131 {
132 "cell_type": "code",
133 "source": [
134 "from IPython.display import Image\n",
135 "Image(\"http://ipython.org/_static/IPy_header.png\")"
136 ],
137 "metadata": {
138 "collapsed": false,
139 "autoscroll": false
140 },
141 "outputs": [
142 {
143 "metadata": {},
144 "output_type": "execute_result",
145 "image/png": "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\n",
146 "prompt_number": 6,
147 "text/plain": [
148 "<IPython.core.display.Image at 0x111275490>"
149 ]
150 }
151 ],
152 "prompt_number": 6
153 }
154 ]
155 }
@@ -1,70 +1,54 b''
1 """API for converting notebooks between versions.
1 """API for converting notebooks between versions."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Jonathan Frederic
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 from .reader import get_version, versions
6 from .reader import get_version, versions
20
7
21 #-----------------------------------------------------------------------------
22 # Functions
23 #-----------------------------------------------------------------------------
24
8
25 def convert(nb, to_version):
9 def convert(nb, to_version):
26 """Convert a notebook node object to a specific version. Assumes that
10 """Convert a notebook node object to a specific version. Assumes that
27 all the versions starting from 1 to the latest major X are implemented.
11 all the versions starting from 1 to the latest major X are implemented.
28 In other words, there should never be a case where v1 v2 v3 v5 exist without
12 In other words, there should never be a case where v1 v2 v3 v5 exist without
29 a v4. Also assumes that all conversions can be made in one step increments
13 a v4. Also assumes that all conversions can be made in one step increments
30 between major versions and ignores minor revisions.
14 between major versions and ignores minor revisions.
31
15
32 Parameters
16 Parameters
33 ----------
17 ----------
34 nb : NotebookNode
18 nb : NotebookNode
35 to_version : int
19 to_version : int
36 Major revision to convert the notebook to. Can either be an upgrade or
20 Major revision to convert the notebook to. Can either be an upgrade or
37 a downgrade.
21 a downgrade.
38 """
22 """
39
23
40 # Get input notebook version.
24 # Get input notebook version.
41 (version, version_minor) = get_version(nb)
25 (version, version_minor) = get_version(nb)
42
26
43 # Check if destination is current version, if so return contents
27 # Check if destination is current version, if so return contents
44 if version == to_version:
28 if version == to_version:
45 return nb
29 return nb
46
30
47 # If the version exist, try to convert to it one step at a time.
31 # If the version exist, try to convert to it one step at a time.
48 elif to_version in versions:
32 elif to_version in versions:
49
33
50 # Get the the version that this recursion will convert to as a step
34 # Get the the version that this recursion will convert to as a step
51 # closer to the final revision. Make sure the newer of the conversion
35 # closer to the final revision. Make sure the newer of the conversion
52 # functions is used to perform the conversion.
36 # functions is used to perform the conversion.
53 if to_version > version:
37 if to_version > version:
54 step_version = version + 1
38 step_version = version + 1
55 convert_function = versions[step_version].upgrade
39 convert_function = versions[step_version].upgrade
56 else:
40 else:
57 step_version = version - 1
41 step_version = version - 1
58 convert_function = versions[version].downgrade
42 convert_function = versions[version].downgrade
59
43
60 # Convert and make sure version changed during conversion.
44 # Convert and make sure version changed during conversion.
61 converted = convert_function(nb)
45 converted = convert_function(nb)
62 if converted.get('nbformat', 1) == version:
46 if converted.get('nbformat', 1) == version:
63 raise Exception("Cannot convert notebook from v%d to v%d. Operation" \
47 raise ValueError("Cannot convert notebook from v%d to v%d. Operation" \
64 "failed silently." % (version, step_version))
48 "failed silently." % (version, step_version))
65
49
66 # Recursively convert until target version is reached.
50 # Recursively convert until target version is reached.
67 return convert(converted, to_version)
51 return convert(converted, to_version)
68 else:
52 else:
69 raise Exception("Cannot convert notebook to v%d because that " \
53 raise ValueError("Cannot convert notebook to v%d because that " \
70 "version doesn't exist" % (to_version))
54 "version doesn't exist" % (to_version))
@@ -1,109 +1,95 b''
1 """API for reading notebooks.
1 """API for reading notebooks of different versions"""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Jonathan Frederic
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import json
6 import json
20
7
21 from . import v1
8 from . import v1
22 from . import v2
9 from . import v2
23 from . import v3
10 from . import v3
11 from . import v4
24
12
25 versions = {
13 versions = {
26 1: v1,
14 1: v1,
27 2: v2,
15 2: v2,
28 3: v3,
16 3: v3,
17 4: v4,
29 }
18 }
30
19
31 #-----------------------------------------------------------------------------
32 # Code
33 #-----------------------------------------------------------------------------
34
20
35 class NotJSONError(ValueError):
21 class NotJSONError(ValueError):
36 pass
22 pass
37
23
38 def parse_json(s, **kwargs):
24 def parse_json(s, **kwargs):
39 """Parse a JSON string into a dict."""
25 """Parse a JSON string into a dict."""
40 try:
26 try:
41 nb_dict = json.loads(s, **kwargs)
27 nb_dict = json.loads(s, **kwargs)
42 except ValueError:
28 except ValueError:
43 # Limit the error message to 80 characters. Display whatever JSON will fit.
29 # Limit the error message to 80 characters. Display whatever JSON will fit.
44 raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...")
30 raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...")
45 return nb_dict
31 return nb_dict
46
32
47 # High level API
33 # High level API
48
34
49 def get_version(nb):
35 def get_version(nb):
50 """Get the version of a notebook.
36 """Get the version of a notebook.
51
37
52 Parameters
38 Parameters
53 ----------
39 ----------
54 nb : dict
40 nb : dict
55 NotebookNode or dict containing notebook data.
41 NotebookNode or dict containing notebook data.
56
42
57 Returns
43 Returns
58 -------
44 -------
59 Tuple containing major (int) and minor (int) version numbers
45 Tuple containing major (int) and minor (int) version numbers
60 """
46 """
61 major = nb.get('nbformat', 1)
47 major = nb.get('nbformat', 1)
62 minor = nb.get('nbformat_minor', 0)
48 minor = nb.get('nbformat_minor', 0)
63 return (major, minor)
49 return (major, minor)
64
50
65
51
66 def reads(s, **kwargs):
52 def reads(s, **kwargs):
67 """Read a notebook from a json string and return the
53 """Read a notebook from a json string and return the
68 NotebookNode object.
54 NotebookNode object.
69
55
70 This function properly reads notebooks of any version. No version
56 This function properly reads notebooks of any version. No version
71 conversion is performed.
57 conversion is performed.
72
58
73 Parameters
59 Parameters
74 ----------
60 ----------
75 s : unicode
61 s : unicode
76 The raw unicode string to read the notebook from.
62 The raw unicode string to read the notebook from.
77
63
78 Returns
64 Returns
79 -------
65 -------
80 nb : NotebookNode
66 nb : NotebookNode
81 The notebook that was read.
67 The notebook that was read.
82 """
68 """
83 from .current import NBFormatError
69 from .current import NBFormatError
84
70
85 nb_dict = parse_json(s, **kwargs)
71 nb_dict = parse_json(s, **kwargs)
86 (major, minor) = get_version(nb_dict)
72 (major, minor) = get_version(nb_dict)
87 if major in versions:
73 if major in versions:
88 return versions[major].to_notebook_json(nb_dict, minor=minor)
74 return versions[major].to_notebook_json(nb_dict, minor=minor)
89 else:
75 else:
90 raise NBFormatError('Unsupported nbformat version %s' % major)
76 raise NBFormatError('Unsupported nbformat version %s' % major)
91
77
92
78
93 def read(fp, **kwargs):
79 def read(fp, **kwargs):
94 """Read a notebook from a file and return the NotebookNode object.
80 """Read a notebook from a file and return the NotebookNode object.
95
81
96 This function properly reads notebooks of any version. No version
82 This function properly reads notebooks of any version. No version
97 conversion is performed.
83 conversion is performed.
98
84
99 Parameters
85 Parameters
100 ----------
86 ----------
101 fp : file
87 fp : file
102 Any file-like object with a read method.
88 Any file-like object with a read method.
103
89
104 Returns
90 Returns
105 -------
91 -------
106 nb : NotebookNode
92 nb : NotebookNode
107 The notebook that was read.
93 The notebook that was read.
108 """
94 """
109 return reads(fp.read(), **kwargs)
95 return reads(fp.read(), **kwargs)
@@ -1,312 +1,310 b''
1 """Functions for signing notebooks"""
1 """Functions for signing notebooks"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import base64
6 import base64
7 from contextlib import contextmanager
7 from contextlib import contextmanager
8 import hashlib
8 import hashlib
9 from hmac import HMAC
9 from hmac import HMAC
10 import io
10 import io
11 import os
11 import os
12
12
13 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
13 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
14 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool
14 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool
15 from IPython.config import LoggingConfigurable, MultipleInstanceError
15 from IPython.config import LoggingConfigurable, MultipleInstanceError
16 from IPython.core.application import BaseIPythonApplication, base_flags
16 from IPython.core.application import BaseIPythonApplication, base_flags
17
17
18 from .current import read, write
18 from .current import read, write
19
19
20
21 try:
20 try:
22 # Python 3
21 # Python 3
23 algorithms = hashlib.algorithms_guaranteed
22 algorithms = hashlib.algorithms_guaranteed
24 except AttributeError:
23 except AttributeError:
25 algorithms = hashlib.algorithms
24 algorithms = hashlib.algorithms
26
25
27
26
28 def yield_everything(obj):
27 def yield_everything(obj):
29 """Yield every item in a container as bytes
28 """Yield every item in a container as bytes
30
29
31 Allows any JSONable object to be passed to an HMAC digester
30 Allows any JSONable object to be passed to an HMAC digester
32 without having to serialize the whole thing.
31 without having to serialize the whole thing.
33 """
32 """
34 if isinstance(obj, dict):
33 if isinstance(obj, dict):
35 for key in sorted(obj):
34 for key in sorted(obj):
36 value = obj[key]
35 value = obj[key]
37 yield cast_bytes(key)
36 yield cast_bytes(key)
38 for b in yield_everything(value):
37 for b in yield_everything(value):
39 yield b
38 yield b
40 elif isinstance(obj, (list, tuple)):
39 elif isinstance(obj, (list, tuple)):
41 for element in obj:
40 for element in obj:
42 for b in yield_everything(element):
41 for b in yield_everything(element):
43 yield b
42 yield b
44 elif isinstance(obj, unicode_type):
43 elif isinstance(obj, unicode_type):
45 yield obj.encode('utf8')
44 yield obj.encode('utf8')
46 else:
45 else:
47 yield unicode_type(obj).encode('utf8')
46 yield unicode_type(obj).encode('utf8')
48
47
49
48
50 @contextmanager
49 @contextmanager
51 def signature_removed(nb):
50 def signature_removed(nb):
52 """Context manager for operating on a notebook with its signature removed
51 """Context manager for operating on a notebook with its signature removed
53
52
54 Used for excluding the previous signature when computing a notebook's signature.
53 Used for excluding the previous signature when computing a notebook's signature.
55 """
54 """
56 save_signature = nb['metadata'].pop('signature', None)
55 save_signature = nb['metadata'].pop('signature', None)
57 try:
56 try:
58 yield
57 yield
59 finally:
58 finally:
60 if save_signature is not None:
59 if save_signature is not None:
61 nb['metadata']['signature'] = save_signature
60 nb['metadata']['signature'] = save_signature
62
61
63
62
64 class NotebookNotary(LoggingConfigurable):
63 class NotebookNotary(LoggingConfigurable):
65 """A class for computing and verifying notebook signatures."""
64 """A class for computing and verifying notebook signatures."""
66
65
67 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
66 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
68 def _profile_dir_default(self):
67 def _profile_dir_default(self):
69 from IPython.core.application import BaseIPythonApplication
68 from IPython.core.application import BaseIPythonApplication
70 app = None
69 app = None
71 try:
70 try:
72 if BaseIPythonApplication.initialized():
71 if BaseIPythonApplication.initialized():
73 app = BaseIPythonApplication.instance()
72 app = BaseIPythonApplication.instance()
74 except MultipleInstanceError:
73 except MultipleInstanceError:
75 pass
74 pass
76 if app is None:
75 if app is None:
77 # create an app, without the global instance
76 # create an app, without the global instance
78 app = BaseIPythonApplication()
77 app = BaseIPythonApplication()
79 app.initialize(argv=[])
78 app.initialize(argv=[])
80 return app.profile_dir
79 return app.profile_dir
81
80
82 algorithm = Enum(algorithms, default_value='sha256', config=True,
81 algorithm = Enum(algorithms, default_value='sha256', config=True,
83 help="""The hashing algorithm used to sign notebooks."""
82 help="""The hashing algorithm used to sign notebooks."""
84 )
83 )
85 def _algorithm_changed(self, name, old, new):
84 def _algorithm_changed(self, name, old, new):
86 self.digestmod = getattr(hashlib, self.algorithm)
85 self.digestmod = getattr(hashlib, self.algorithm)
87
86
88 digestmod = Any()
87 digestmod = Any()
89 def _digestmod_default(self):
88 def _digestmod_default(self):
90 return getattr(hashlib, self.algorithm)
89 return getattr(hashlib, self.algorithm)
91
90
92 secret_file = Unicode(config=True,
91 secret_file = Unicode(config=True,
93 help="""The file where the secret key is stored."""
92 help="""The file where the secret key is stored."""
94 )
93 )
95 def _secret_file_default(self):
94 def _secret_file_default(self):
96 if self.profile_dir is None:
95 if self.profile_dir is None:
97 return ''
96 return ''
98 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
97 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
99
98
100 secret = Bytes(config=True,
99 secret = Bytes(config=True,
101 help="""The secret key with which notebooks are signed."""
100 help="""The secret key with which notebooks are signed."""
102 )
101 )
103 def _secret_default(self):
102 def _secret_default(self):
104 # note : this assumes an Application is running
103 # note : this assumes an Application is running
105 if os.path.exists(self.secret_file):
104 if os.path.exists(self.secret_file):
106 with io.open(self.secret_file, 'rb') as f:
105 with io.open(self.secret_file, 'rb') as f:
107 return f.read()
106 return f.read()
108 else:
107 else:
109 secret = base64.encodestring(os.urandom(1024))
108 secret = base64.encodestring(os.urandom(1024))
110 self._write_secret_file(secret)
109 self._write_secret_file(secret)
111 return secret
110 return secret
112
111
113 def _write_secret_file(self, secret):
112 def _write_secret_file(self, secret):
114 """write my secret to my secret_file"""
113 """write my secret to my secret_file"""
115 self.log.info("Writing notebook-signing key to %s", self.secret_file)
114 self.log.info("Writing notebook-signing key to %s", self.secret_file)
116 with io.open(self.secret_file, 'wb') as f:
115 with io.open(self.secret_file, 'wb') as f:
117 f.write(secret)
116 f.write(secret)
118 try:
117 try:
119 os.chmod(self.secret_file, 0o600)
118 os.chmod(self.secret_file, 0o600)
120 except OSError:
119 except OSError:
121 self.log.warn(
120 self.log.warn(
122 "Could not set permissions on %s",
121 "Could not set permissions on %s",
123 self.secret_file
122 self.secret_file
124 )
123 )
125 return secret
124 return secret
126
125
127 def compute_signature(self, nb):
126 def compute_signature(self, nb):
128 """Compute a notebook's signature
127 """Compute a notebook's signature
129
128
130 by hashing the entire contents of the notebook via HMAC digest.
129 by hashing the entire contents of the notebook via HMAC digest.
131 """
130 """
132 hmac = HMAC(self.secret, digestmod=self.digestmod)
131 hmac = HMAC(self.secret, digestmod=self.digestmod)
133 # don't include the previous hash in the content to hash
132 # don't include the previous hash in the content to hash
134 with signature_removed(nb):
133 with signature_removed(nb):
135 # sign the whole thing
134 # sign the whole thing
136 for b in yield_everything(nb):
135 for b in yield_everything(nb):
137 hmac.update(b)
136 hmac.update(b)
138
137
139 return hmac.hexdigest()
138 return hmac.hexdigest()
140
139
141 def check_signature(self, nb):
140 def check_signature(self, nb):
142 """Check a notebook's stored signature
141 """Check a notebook's stored signature
143
142
144 If a signature is stored in the notebook's metadata,
143 If a signature is stored in the notebook's metadata,
145 a new signature is computed and compared with the stored value.
144 a new signature is computed and compared with the stored value.
146
145
147 Returns True if the signature is found and matches, False otherwise.
146 Returns True if the signature is found and matches, False otherwise.
148
147
149 The following conditions must all be met for a notebook to be trusted:
148 The following conditions must all be met for a notebook to be trusted:
150 - a signature is stored in the form 'scheme:hexdigest'
149 - a signature is stored in the form 'scheme:hexdigest'
151 - the stored scheme matches the requested scheme
150 - the stored scheme matches the requested scheme
152 - the requested scheme is available from hashlib
151 - the requested scheme is available from hashlib
153 - the computed hash from notebook_signature matches the stored hash
152 - the computed hash from notebook_signature matches the stored hash
154 """
153 """
155 stored_signature = nb['metadata'].get('signature', None)
154 stored_signature = nb['metadata'].get('signature', None)
156 if not stored_signature \
155 if not stored_signature \
157 or not isinstance(stored_signature, string_types) \
156 or not isinstance(stored_signature, string_types) \
158 or ':' not in stored_signature:
157 or ':' not in stored_signature:
159 return False
158 return False
160 stored_algo, sig = stored_signature.split(':', 1)
159 stored_algo, sig = stored_signature.split(':', 1)
161 if self.algorithm != stored_algo:
160 if self.algorithm != stored_algo:
162 return False
161 return False
163 my_signature = self.compute_signature(nb)
162 my_signature = self.compute_signature(nb)
164 return my_signature == sig
163 return my_signature == sig
165
164
166 def sign(self, nb):
165 def sign(self, nb):
167 """Sign a notebook, indicating that its output is trusted
166 """Sign a notebook, indicating that its output is trusted
168
167
169 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
168 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
170
169
171 e.g. 'sha256:deadbeef123...'
170 e.g. 'sha256:deadbeef123...'
172 """
171 """
173 signature = self.compute_signature(nb)
172 signature = self.compute_signature(nb)
174 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
173 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
175
174
176 def mark_cells(self, nb, trusted):
175 def mark_cells(self, nb, trusted):
177 """Mark cells as trusted if the notebook's signature can be verified
176 """Mark cells as trusted if the notebook's signature can be verified
178
177
179 Sets ``cell.metadata.trusted = True | False`` on all code cells,
178 Sets ``cell.metadata.trusted = True | False`` on all code cells,
180 depending on whether the stored signature can be verified.
179 depending on whether the stored signature can be verified.
181
180
182 This function is the inverse of check_cells
181 This function is the inverse of check_cells
183 """
182 """
184 if not nb['worksheets']:
183 if not nb['cells']:
185 # nothing to mark if there are no cells
184 # nothing to mark if there are no cells
186 return
185 return
187 for cell in nb['worksheets'][0]['cells']:
186 for cell in nb['cells']:
188 if cell['cell_type'] == 'code':
187 if cell['cell_type'] == 'code':
189 cell['metadata']['trusted'] = trusted
188 cell['metadata']['trusted'] = trusted
190
189
191 def _check_cell(self, cell):
190 def _check_cell(self, cell):
192 """Do we trust an individual cell?
191 """Do we trust an individual cell?
193
192
194 Return True if:
193 Return True if:
195
194
196 - cell is explicitly trusted
195 - cell is explicitly trusted
197 - cell has no potentially unsafe rich output
196 - cell has no potentially unsafe rich output
198
197
199 If a cell has no output, or only simple print statements,
198 If a cell has no output, or only simple print statements,
200 it will always be trusted.
199 it will always be trusted.
201 """
200 """
202 # explicitly trusted
201 # explicitly trusted
203 if cell['metadata'].pop("trusted", False):
202 if cell['metadata'].pop("trusted", False):
204 return True
203 return True
205
204
206 # explicitly safe output
205 # explicitly safe output
207 safe = {
206 safe = {
208 'text/plain', 'image/png', 'image/jpeg',
207 'text/plain', 'image/png', 'image/jpeg',
209 'text', 'png', 'jpg', # v3-style short keys
210 }
208 }
211
209
212 for output in cell['outputs']:
210 for output in cell['outputs']:
213 output_type = output['output_type']
211 output_type = output['output_type']
214 if output_type in ('pyout', 'display_data'):
212 if output_type in {'execute_result', 'display_data'}:
215 # if there are any data keys not in the safe whitelist
213 # if there are any data keys not in the safe whitelist
216 output_keys = set(output).difference({"output_type", "prompt_number", "metadata"})
214 output_keys = set(output).difference({"output_type", "prompt_number", "metadata"})
217 if output_keys.difference(safe):
215 if output_keys.difference(safe):
218 return False
216 return False
219
217
220 return True
218 return True
221
219
222 def check_cells(self, nb):
220 def check_cells(self, nb):
223 """Return whether all code cells are trusted
221 """Return whether all code cells are trusted
224
222
225 If there are no code cells, return True.
223 If there are no code cells, return True.
226
224
227 This function is the inverse of mark_cells.
225 This function is the inverse of mark_cells.
228 """
226 """
229 if not nb['worksheets']:
227 if not nb['cells']:
230 return True
228 return True
231 trusted = True
229 trusted = True
232 for cell in nb['worksheets'][0]['cells']:
230 for cell in nb['cells']:
233 if cell['cell_type'] != 'code':
231 if cell['cell_type'] != 'code':
234 continue
232 continue
235 # only distrust a cell if it actually has some output to distrust
233 # only distrust a cell if it actually has some output to distrust
236 if not self._check_cell(cell):
234 if not self._check_cell(cell):
237 trusted = False
235 trusted = False
238
236
239 return trusted
237 return trusted
240
238
241
239
242 trust_flags = {
240 trust_flags = {
243 'reset' : (
241 'reset' : (
244 {'TrustNotebookApp' : { 'reset' : True}},
242 {'TrustNotebookApp' : { 'reset' : True}},
245 """Generate a new key for notebook signature.
243 """Generate a new key for notebook signature.
246 All previously signed notebooks will become untrusted.
244 All previously signed notebooks will become untrusted.
247 """
245 """
248 ),
246 ),
249 }
247 }
250 trust_flags.update(base_flags)
248 trust_flags.update(base_flags)
251 trust_flags.pop('init')
249 trust_flags.pop('init')
252
250
253
251
254 class TrustNotebookApp(BaseIPythonApplication):
252 class TrustNotebookApp(BaseIPythonApplication):
255
253
256 description="""Sign one or more IPython notebooks with your key,
254 description="""Sign one or more IPython notebooks with your key,
257 to trust their dynamic (HTML, Javascript) output.
255 to trust their dynamic (HTML, Javascript) output.
258
256
259 Trusting a notebook only applies to the current IPython profile.
257 Trusting a notebook only applies to the current IPython profile.
260 To trust a notebook for use with a profile other than default,
258 To trust a notebook for use with a profile other than default,
261 add `--profile [profile name]`.
259 add `--profile [profile name]`.
262
260
263 Otherwise, you will have to re-execute the notebook to see output.
261 Otherwise, you will have to re-execute the notebook to see output.
264 """
262 """
265
263
266 examples = """
264 examples = """
267 ipython trust mynotebook.ipynb and_this_one.ipynb
265 ipython trust mynotebook.ipynb and_this_one.ipynb
268 ipython trust --profile myprofile mynotebook.ipynb
266 ipython trust --profile myprofile mynotebook.ipynb
269 """
267 """
270
268
271 flags = trust_flags
269 flags = trust_flags
272
270
273 reset = Bool(False, config=True,
271 reset = Bool(False, config=True,
274 help="""If True, generate a new key for notebook signature.
272 help="""If True, generate a new key for notebook signature.
275 After reset, all previously signed notebooks will become untrusted.
273 After reset, all previously signed notebooks will become untrusted.
276 """
274 """
277 )
275 )
278
276
279 notary = Instance(NotebookNotary)
277 notary = Instance(NotebookNotary)
280 def _notary_default(self):
278 def _notary_default(self):
281 return NotebookNotary(parent=self, profile_dir=self.profile_dir)
279 return NotebookNotary(parent=self, profile_dir=self.profile_dir)
282
280
283 def sign_notebook(self, notebook_path):
281 def sign_notebook(self, notebook_path):
284 if not os.path.exists(notebook_path):
282 if not os.path.exists(notebook_path):
285 self.log.error("Notebook missing: %s" % notebook_path)
283 self.log.error("Notebook missing: %s" % notebook_path)
286 self.exit(1)
284 self.exit(1)
287 with io.open(notebook_path, encoding='utf8') as f:
285 with io.open(notebook_path, encoding='utf8') as f:
288 nb = read(f, 'json')
286 nb = read(f, 'json')
289 if self.notary.check_signature(nb):
287 if self.notary.check_signature(nb):
290 print("Notebook already signed: %s" % notebook_path)
288 print("Notebook already signed: %s" % notebook_path)
291 else:
289 else:
292 print("Signing notebook: %s" % notebook_path)
290 print("Signing notebook: %s" % notebook_path)
293 self.notary.sign(nb)
291 self.notary.sign(nb)
294 with io.open(notebook_path, 'w', encoding='utf8') as f:
292 with io.open(notebook_path, 'w', encoding='utf8') as f:
295 write(nb, f, 'json')
293 write(nb, f, 'json')
296
294
297 def generate_new_key(self):
295 def generate_new_key(self):
298 """Generate a new notebook signature key"""
296 """Generate a new notebook signature key"""
299 print("Generating new notebook key: %s" % self.notary.secret_file)
297 print("Generating new notebook key: %s" % self.notary.secret_file)
300 self.notary._write_secret_file(os.urandom(1024))
298 self.notary._write_secret_file(os.urandom(1024))
301
299
302 def start(self):
300 def start(self):
303 if self.reset:
301 if self.reset:
304 self.generate_new_key()
302 self.generate_new_key()
305 return
303 return
306 if not self.extra_args:
304 if not self.extra_args:
307 self.log.critical("Specify at least one notebook to sign.")
305 self.log.critical("Specify at least one notebook to sign.")
308 self.exit(1)
306 self.exit(1)
309
307
310 for notebook_path in self.extra_args:
308 for notebook_path in self.extra_args:
311 self.sign_notebook(notebook_path)
309 self.sign_notebook(notebook_path)
312
310
@@ -1,69 +1,57 b''
1 """
1 """Tests for nbformat.convert"""
2 Contains tests class for convert.py
2
3 """
3 # Copyright (c) IPython Development Team.
4 #-----------------------------------------------------------------------------
4 # Distributed under the terms of the Modified BSD License.
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
5
15 from .base import TestsBase
6 from .base import TestsBase
16
7
17 from ..convert import convert
8 from ..convert import convert
18 from ..reader import read, get_version
9 from ..reader import read, get_version
19 from ..current import current_nbformat
10 from ..current import current_nbformat
20
11
21 #-----------------------------------------------------------------------------
22 # Classes and functions
23 #-----------------------------------------------------------------------------
24
12
25 class TestConvert(TestsBase):
13 class TestConvert(TestsBase):
26
14
27 def test_downgrade(self):
15 def test_downgrade_3_2(self):
28 """Do notebook downgrades work?"""
16 """Do notebook downgrades work?"""
29
17
30 # Open a version 3 notebook and attempt to downgrade it to version 2.
18 # Open a version 3 notebook and attempt to downgrade it to version 2.
31 with self.fopen(u'test3.ipynb', u'r') as f:
19 with self.fopen(u'test3.ipynb', u'r') as f:
32 nb = read(f)
20 nb = read(f)
33 nb = convert(nb, 2)
21 nb = convert(nb, 2)
34
22
35 # Check if downgrade was successful.
23 # Check if downgrade was successful.
36 (major, minor) = get_version(nb)
24 (major, minor) = get_version(nb)
37 self.assertEqual(major, 2)
25 self.assertEqual(major, 2)
38
26
39
27
40 def test_upgrade(self):
28 def test_upgrade_2_3(self):
41 """Do notebook upgrades work?"""
29 """Do notebook upgrades work?"""
42
30
43 # Open a version 2 notebook and attempt to upgrade it to version 3.
31 # Open a version 2 notebook and attempt to upgrade it to version 3.
44 with self.fopen(u'test2.ipynb', u'r') as f:
32 with self.fopen(u'test2.ipynb', u'r') as f:
45 nb = read(f)
33 nb = read(f)
46 nb = convert(nb, 3)
34 nb = convert(nb, 3)
47
35
48 # Check if upgrade was successful.
36 # Check if upgrade was successful.
49 (major, minor) = get_version(nb)
37 (major, minor) = get_version(nb)
50 self.assertEqual(major, 3)
38 self.assertEqual(major, 3)
51
39
52
40
53 def test_open_current(self):
41 def test_open_current(self):
54 """Can an old notebook be opened and converted to the current version
42 """Can an old notebook be opened and converted to the current version
55 while remembering the original version of the notebook?"""
43 while remembering the original version of the notebook?"""
56
44
57 # Open a version 2 notebook and attempt to upgrade it to the current version
45 # Open a version 2 notebook and attempt to upgrade it to the current version
58 # while remembering it's version information.
46 # while remembering it's version information.
59 with self.fopen(u'test2.ipynb', u'r') as f:
47 with self.fopen(u'test2.ipynb', u'r') as f:
60 nb = read(f)
48 nb = read(f)
61 (original_major, original_minor) = get_version(nb)
49 (original_major, original_minor) = get_version(nb)
62 nb = convert(nb, current_nbformat)
50 nb = convert(nb, current_nbformat)
63
51
64 # Check if upgrade was successful.
52 # Check if upgrade was successful.
65 (major, minor) = get_version(nb)
53 (major, minor) = get_version(nb)
66 self.assertEqual(major, current_nbformat)
54 self.assertEqual(major, current_nbformat)
67
55
68 # Check if the original major revision was remembered.
56 # Check if the original major revision was remembered.
69 self.assertEqual(original_major, 2)
57 self.assertEqual(original_major, 2)
@@ -1,122 +1,112 b''
1 """Test Notebook signing"""
1 """Test Notebook signing"""
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2014, The IPython Development Team
4 #
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
8
2
9 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
10 # Imports
4 # Distributed under the terms of the Modified BSD License.
11 #-----------------------------------------------------------------------------
12
5
13 from .. import sign
6 from .. import sign
14 from .base import TestsBase
7 from .base import TestsBase
15
8
16 from ..current import read
9 from ..current import read
17 from IPython.core.getipython import get_ipython
10 from IPython.core.getipython import get_ipython
18
11
19 #-----------------------------------------------------------------------------
20 # Classes and functions
21 #-----------------------------------------------------------------------------
22
12
23 class TestNotary(TestsBase):
13 class TestNotary(TestsBase):
24
14
25 def setUp(self):
15 def setUp(self):
26 self.notary = sign.NotebookNotary(
16 self.notary = sign.NotebookNotary(
27 secret=b'secret',
17 secret=b'secret',
28 profile_dir=get_ipython().profile_dir
18 profile_dir=get_ipython().profile_dir
29 )
19 )
30 with self.fopen(u'test3.ipynb', u'r') as f:
20 with self.fopen(u'test3.ipynb', u'r') as f:
31 self.nb = read(f, u'json')
21 self.nb = read(f, u'json')
32
22
33 def test_algorithms(self):
23 def test_algorithms(self):
34 last_sig = ''
24 last_sig = ''
35 for algo in sign.algorithms:
25 for algo in sign.algorithms:
36 self.notary.algorithm = algo
26 self.notary.algorithm = algo
37 self.notary.sign(self.nb)
27 self.notary.sign(self.nb)
38 sig = self.nb.metadata.signature
28 sig = self.nb.metadata.signature
39 print(sig)
29 print(sig)
40 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
30 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
41 self.assertNotEqual(last_sig, sig)
31 self.assertNotEqual(last_sig, sig)
42 last_sig = sig
32 last_sig = sig
43
33
44 def test_sign_same(self):
34 def test_sign_same(self):
45 """Multiple signatures of the same notebook are the same"""
35 """Multiple signatures of the same notebook are the same"""
46 sig1 = self.notary.compute_signature(self.nb)
36 sig1 = self.notary.compute_signature(self.nb)
47 sig2 = self.notary.compute_signature(self.nb)
37 sig2 = self.notary.compute_signature(self.nb)
48 self.assertEqual(sig1, sig2)
38 self.assertEqual(sig1, sig2)
49
39
50 def test_change_secret(self):
40 def test_change_secret(self):
51 """Changing the secret changes the signature"""
41 """Changing the secret changes the signature"""
52 sig1 = self.notary.compute_signature(self.nb)
42 sig1 = self.notary.compute_signature(self.nb)
53 self.notary.secret = b'different'
43 self.notary.secret = b'different'
54 sig2 = self.notary.compute_signature(self.nb)
44 sig2 = self.notary.compute_signature(self.nb)
55 self.assertNotEqual(sig1, sig2)
45 self.assertNotEqual(sig1, sig2)
56
46
57 def test_sign(self):
47 def test_sign(self):
58 self.notary.sign(self.nb)
48 self.notary.sign(self.nb)
59 sig = self.nb.metadata.signature
49 sig = self.nb.metadata.signature
60 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
50 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
61
51
62 def test_check_signature(self):
52 def test_check_signature(self):
63 nb = self.nb
53 nb = self.nb
64 md = nb.metadata
54 md = nb.metadata
65 notary = self.notary
55 notary = self.notary
66 check_signature = notary.check_signature
56 check_signature = notary.check_signature
67 # no signature:
57 # no signature:
68 md.pop('signature', None)
58 md.pop('signature', None)
69 self.assertFalse(check_signature(nb))
59 self.assertFalse(check_signature(nb))
70 # hash only, no algo
60 # hash only, no algo
71 md.signature = notary.compute_signature(nb)
61 md.signature = notary.compute_signature(nb)
72 self.assertFalse(check_signature(nb))
62 self.assertFalse(check_signature(nb))
73 # proper signature, algo mismatch
63 # proper signature, algo mismatch
74 notary.algorithm = 'sha224'
64 notary.algorithm = 'sha224'
75 notary.sign(nb)
65 notary.sign(nb)
76 notary.algorithm = 'sha256'
66 notary.algorithm = 'sha256'
77 self.assertFalse(check_signature(nb))
67 self.assertFalse(check_signature(nb))
78 # check correctly signed notebook
68 # check correctly signed notebook
79 notary.sign(nb)
69 notary.sign(nb)
80 self.assertTrue(check_signature(nb))
70 self.assertTrue(check_signature(nb))
81
71
82 def test_mark_cells_untrusted(self):
72 def test_mark_cells_untrusted(self):
83 cells = self.nb.worksheets[0].cells
73 cells = self.nb.cells
84 self.notary.mark_cells(self.nb, False)
74 self.notary.mark_cells(self.nb, False)
85 for cell in cells:
75 for cell in cells:
86 self.assertNotIn('trusted', cell)
76 self.assertNotIn('trusted', cell)
87 if cell.cell_type == 'code':
77 if cell.cell_type == 'code':
88 self.assertIn('trusted', cell.metadata)
78 self.assertIn('trusted', cell.metadata)
89 self.assertFalse(cell.metadata.trusted)
79 self.assertFalse(cell.metadata.trusted)
90 else:
80 else:
91 self.assertNotIn('trusted', cell.metadata)
81 self.assertNotIn('trusted', cell.metadata)
92
82
93 def test_mark_cells_trusted(self):
83 def test_mark_cells_trusted(self):
94 cells = self.nb.worksheets[0].cells
84 cells = self.nb.cells
95 self.notary.mark_cells(self.nb, True)
85 self.notary.mark_cells(self.nb, True)
96 for cell in cells:
86 for cell in cells:
97 self.assertNotIn('trusted', cell)
87 self.assertNotIn('trusted', cell)
98 if cell.cell_type == 'code':
88 if cell.cell_type == 'code':
99 self.assertIn('trusted', cell.metadata)
89 self.assertIn('trusted', cell.metadata)
100 self.assertTrue(cell.metadata.trusted)
90 self.assertTrue(cell.metadata.trusted)
101 else:
91 else:
102 self.assertNotIn('trusted', cell.metadata)
92 self.assertNotIn('trusted', cell.metadata)
103
93
104 def test_check_cells(self):
94 def test_check_cells(self):
105 nb = self.nb
95 nb = self.nb
106 self.notary.mark_cells(nb, True)
96 self.notary.mark_cells(nb, True)
107 self.assertTrue(self.notary.check_cells(nb))
97 self.assertTrue(self.notary.check_cells(nb))
108 for cell in nb.worksheets[0].cells:
98 for cell in nb.cells:
109 self.assertNotIn('trusted', cell)
99 self.assertNotIn('trusted', cell)
110 self.notary.mark_cells(nb, False)
100 self.notary.mark_cells(nb, False)
111 self.assertFalse(self.notary.check_cells(nb))
101 self.assertFalse(self.notary.check_cells(nb))
112 for cell in nb.worksheets[0].cells:
102 for cell in nb.cells:
113 self.assertNotIn('trusted', cell)
103 self.assertNotIn('trusted', cell)
114
104
115 def test_trust_no_output(self):
105 def test_trust_no_output(self):
116 nb = self.nb
106 nb = self.nb
117 self.notary.mark_cells(nb, False)
107 self.notary.mark_cells(nb, False)
118 for cell in nb.worksheets[0].cells:
108 for cell in nb.cells:
119 if cell.cell_type == 'code':
109 if cell.cell_type == 'code':
120 cell.outputs = []
110 cell.outputs = []
121 self.assertTrue(self.notary.check_cells(nb))
111 self.assertTrue(self.notary.check_cells(nb))
122
112
@@ -1,54 +1,57 b''
1 """Test nbformat.validator"""
1 """Test nbformat.validator"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import os
6 import os
7
7
8 from .base import TestsBase
8 from .base import TestsBase
9 from jsonschema import ValidationError
9 from jsonschema import ValidationError
10 from ..current import read
10 from ..current import read
11 from ..validator import isvalid, validate
11 from ..validator import isvalid, validate
12
12
13
13
14 #-----------------------------------------------------------------------------
15 # Classes and functions
16 #-----------------------------------------------------------------------------
17
18 class TestValidator(TestsBase):
14 class TestValidator(TestsBase):
19
15
20 def test_nb2(self):
16 def test_nb2(self):
21 """Test that a v2 notebook converted to v3 passes validation"""
17 """Test that a v2 notebook converted to current passes validation"""
22 with self.fopen(u'test2.ipynb', u'r') as f:
18 with self.fopen(u'test2.ipynb', u'r') as f:
23 nb = read(f, u'json')
19 nb = read(f, u'json')
24 validate(nb)
20 validate(nb)
25 self.assertEqual(isvalid(nb), True)
21 self.assertEqual(isvalid(nb), True)
26
22
27 def test_nb3(self):
23 def test_nb3(self):
28 """Test that a v3 notebook passes validation"""
24 """Test that a v3 notebook passes validation"""
29 with self.fopen(u'test3.ipynb', u'r') as f:
25 with self.fopen(u'test3.ipynb', u'r') as f:
30 nb = read(f, u'json')
26 nb = read(f, u'json')
31 validate(nb)
27 validate(nb)
32 self.assertEqual(isvalid(nb), True)
28 self.assertEqual(isvalid(nb), True)
33
29
30 def test_nb4(self):
31 """Test that a v3 notebook passes validation"""
32 with self.fopen(u'test4.ipynb', u'r') as f:
33 nb = read(f, u'json')
34 validate(nb)
35 self.assertEqual(isvalid(nb), True)
36
34 def test_invalid(self):
37 def test_invalid(self):
35 """Test than an invalid notebook does not pass validation"""
38 """Test than an invalid notebook does not pass validation"""
36 # this notebook has a few different errors:
39 # this notebook has a few different errors:
37 # - the name is an integer, rather than a string
40 # - the name is an integer, rather than a string
38 # - one cell is missing its source
41 # - one cell is missing its source
39 # - one cell has an invalid level
42 # - one cell has an invalid level
40 with self.fopen(u'invalid.ipynb', u'r') as f:
43 with self.fopen(u'invalid.ipynb', u'r') as f:
41 nb = read(f, u'json')
44 nb = read(f, u'json')
42 with self.assertRaises(ValidationError):
45 with self.assertRaises(ValidationError):
43 validate(nb)
46 validate(nb)
44 self.assertEqual(isvalid(nb), False)
47 self.assertEqual(isvalid(nb), False)
45
48
46 def test_future(self):
49 def test_future(self):
47 """Test than a notebook from the future with extra keys passes validation"""
50 """Test than a notebook from the future with extra keys passes validation"""
48 with self.fopen(u'test3plus.ipynb', u'r') as f:
51 with self.fopen(u'test3plus.ipynb', u'r') as f:
49 nb = read(f)
52 nb = read(f)
50 with self.assertRaises(ValidationError):
53 with self.assertRaises(ValidationError):
51 validate(nb, version=3)
54 validate(nb, version=3)
52
55
53 self.assertEqual(isvalid(nb, version=3), False)
56 self.assertEqual(isvalid(nb, version=3), False)
54 self.assertEqual(isvalid(nb), True)
57 self.assertEqual(isvalid(nb), True)
@@ -1,19 +1,19 b''
1 """The main API for the v4 notebook format."""
1 """The main API for the v4 notebook format."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from .nbbase import (
6 from .nbbase import (
7 NotebookNode,
7 NotebookNode, from_dict,
8 nbformat, nbformat_minor, nbformat_schema,
8 nbformat, nbformat_minor, nbformat_schema,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
9 new_code_cell, new_heading_cell, new_markdown_cell, new_notebook,
10 new_output,
10 new_output,
11 )
11 )
12
12
13 from .nbjson import reads as reads_json, writes as writes_json
13 from .nbjson import reads as reads_json, writes as writes_json
14 from .nbjson import reads as read_json, writes as write_json
14 from .nbjson import reads as read_json, writes as write_json
15 from .nbjson import to_notebook as to_notebook_json
15 from .nbjson import to_notebook as to_notebook_json
16
16
17 from .convert import downgrade, upgrade
17 from .convert import downgrade, upgrade
18
18
19
19
General Comments 0
You need to be logged in to leave comments. Login now