Skip to content

Commit 9934706

Browse files
committed
[ExecuteTime] add preprocessor to execute notebook updating timing metadata
1 parent f105d1d commit 9934706

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

docs/source/exporting.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ Generic documentation for preprocessors can be found at
2121
`nbconvert.readthedocs.io/en/latest/api/preprocessors.html <http://nbconvert.readthedocs.io/en/latest/api/preprocessors.html>`__.
2222

2323

24+
Executing and updating timing metadata
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
.. autoclass:: ExecuteTimePreprocessor
28+
29+
2430
Retaining Codefolding
2531
^^^^^^^^^^^^^^^^^^^^^
2632

src/jupyter_contrib_nbextensions/nbconvert_support/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .collapsible_headings import ExporterCollapsibleHeadings
66
from .embedhtml import EmbedHTMLExporter
7+
from .execute_time import ExecuteTimePreprocessor
78
from .exporter_inliner import ExporterInliner
89
from .nbTranslate import NotebookLangExporter
910
from .pp_highlighter import HighlighterPostProcessor, HighlighterPreprocessor
@@ -16,6 +17,7 @@
1617
'templates_directory',
1718
'CodeFoldingPreprocessor',
1819
'EmbedHTMLExporter',
20+
'ExecuteTimePreprocessor',
1921
'ExporterCollapsibleHeadings',
2022
'ExporterInliner',
2123
'HighlighterPostProcessor',
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Module containing a preprocessor tto execute code cells, updating time metadata
3+
"""
4+
5+
from datetime import datetime
6+
7+
from nbconvert.preprocessors.execute import ExecutePreprocessor
8+
9+
10+
class ExecuteTimePreprocessor(ExecutePreprocessor):
11+
"""
12+
Executes all the cells in a notebook, updating their ExecuteTime metadata.
13+
"""
14+
15+
def run_cell(self, cell, cell_index, *args, **kwargs):
16+
before = datetime.utcnow()
17+
exec_reply, outs = super(ExecuteTimePreprocessor, self).run_cell(
18+
cell, cell_index, *args, **kwargs)
19+
20+
if exec_reply.get('msg_type', '') == 'execute_reply':
21+
ets = cell.setdefault('metadata', {}).setdefault('ExecuteTime', {})
22+
if 'started' in exec_reply.get('metadata', {}):
23+
# started value should is already a string
24+
ets['start_time'] = exec_reply['metadata']['started']
25+
else:
26+
# attempt to fallback to datetime obj for execution request msg
27+
ets['start_time'] = exec_reply.get('parent_header', {}).get(
28+
'date', before).isoformat()
29+
ets['end_time'] = (exec_reply.get('header', {}).get(
30+
'date', None) or datetime.utcnow()).isoformat()
31+
32+
return exec_reply, outs

tests/test_preprocessors.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# -*- coding: utf-8 -*-
22

3+
import json
34
import os
45

56
import nbformat.v4 as nbf
6-
from nbconvert import LatexExporter, RSTExporter
7+
from nbconvert import LatexExporter, NotebookExporter, RSTExporter
78
from nbconvert.utils.pandoc import PandocMissing
89
from nose.plugins.skip import SkipTest
9-
from nose.tools import assert_in, assert_not_in, assert_true
10+
from nose.tools import (
11+
assert_greater_equal, assert_in, assert_not_in, assert_true,
12+
)
1013
from traitlets.config import Config
1114

1215

@@ -107,3 +110,27 @@ def test_preprocessor_svg2pdf():
107110
assert_true(pdf_existed, 'exported pdf should exist')
108111
assert_in('test.pdf', body,
109112
'exported pdf should be referenced in exported notebook')
113+
114+
115+
def test_preprocessor_execute_time():
116+
"""Test ExecuteTime preprocessor."""
117+
# check import shortcut
118+
from jupyter_contrib_nbextensions.nbconvert_support import ExecuteTimePreprocessor # noqa E501
119+
notebook_node = nbf.new_notebook(cells=[
120+
nbf.new_code_cell(source="a = 'world'"),
121+
nbf.new_code_cell(source="import time\ntime.sleep(2)"),
122+
])
123+
body, resources = export_through_preprocessor(
124+
notebook_node, ExecuteTimePreprocessor, NotebookExporter, 'ipynb')
125+
cells = json.loads(body)['cells']
126+
for cell in cells:
127+
if cell['cell_type'] != 'code':
128+
assert_not_in('ExecuteTime', cell['metadata'])
129+
else:
130+
assert_in('ExecuteTime', cell['metadata'])
131+
etmd = cell['metadata']['ExecuteTime']
132+
assert_in('start_time', etmd)
133+
assert_in('end_time', etmd)
134+
assert_greater_equal(
135+
etmd['end_time'], etmd['start_time'],
136+
'end_time should be after start time')

0 commit comments

Comments
 (0)