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