Skip to content

Commit e3af22d

Browse files
authored
Merge pull request #1139 from jcb91/execute_time
[ExecuteTime] add preprocessor to execute notebook updating timing metadata
2 parents 89f2aff + c8c74c2 commit e3af22d

File tree

4 files changed

+89
-2
lines changed

4 files changed

+89
-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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
try:
10+
# notebook >= 5.0.0-rc1
11+
import notebook._tz as nbtz
12+
except ImportError:
13+
# notebook < 5.0.0-rc1
14+
import notebook.services.contents.tz as nbtz
15+
16+
17+
class ExecuteTimePreprocessor(ExecutePreprocessor):
18+
"""
19+
Executes all the cells in a notebook, updating their ExecuteTime metadata.
20+
"""
21+
22+
def run_cell(self, cell, cell_index, *args, **kwargs):
23+
before = datetime.utcnow()
24+
exec_reply, outs = super(ExecuteTimePreprocessor, self).run_cell(
25+
cell, cell_index, *args, **kwargs)
26+
27+
if exec_reply.get('msg_type', '') == 'execute_reply':
28+
ets = cell.setdefault('metadata', {}).setdefault('ExecuteTime', {})
29+
if 'started' in exec_reply.get('metadata', {}):
30+
# started value should is already a string, so don't isoformat
31+
ets['start_time'] = exec_reply['metadata']['started']
32+
else:
33+
# attempt to fallback to datetime obj for execution request msg
34+
ets['start_time'] = exec_reply.get(
35+
'parent_header', {}).get('date', before).isoformat()
36+
ets['end_time'] = (
37+
exec_reply.get('header', {}).get('date') or nbtz.utcnow()
38+
).isoformat()
39+
40+
return exec_reply, outs

tests/test_preprocessors.py

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

3+
import json
34
import os
5+
import re
46

57
import nbformat.v4 as nbf
6-
from nbconvert import LatexExporter, RSTExporter
8+
from nbconvert import LatexExporter, NotebookExporter, RSTExporter
79
from nbconvert.utils.pandoc import PandocMissing
810
from nose.plugins.skip import SkipTest
9-
from nose.tools import assert_in, assert_not_in, assert_true
11+
from nose.tools import (
12+
assert_greater_equal, assert_in, assert_not_in, assert_true,
13+
)
1014
from traitlets.config import Config
1115

1216

@@ -107,3 +111,38 @@ def test_preprocessor_svg2pdf():
107111
assert_true(pdf_existed, 'exported pdf should exist')
108112
assert_in('test.pdf', body,
109113
'exported pdf should be referenced in exported notebook')
114+
115+
116+
def _normalize_iso8601_timezone(timestamp_str):
117+
# Zulu -> +00:00 offset
118+
timestamp_str = re.sub(r'Z$', r'+00:00', timestamp_str)
119+
# HH -> HH:00 offset
120+
timestamp_str = re.sub(r'([+-]\d\d)$', r'\1:00', timestamp_str)
121+
# HHMM -> HH:MM offset
122+
timestamp_str = re.sub(r'([+-]\d\d):?(\d\d)$', r'\1:\2', timestamp_str)
123+
return timestamp_str
124+
125+
126+
def test_preprocessor_execute_time():
127+
"""Test ExecuteTime preprocessor."""
128+
# check import shortcut
129+
from jupyter_contrib_nbextensions.nbconvert_support import ExecuteTimePreprocessor # noqa E501
130+
notebook_node = nbf.new_notebook(cells=[
131+
nbf.new_code_cell(source="a = 'world'"),
132+
nbf.new_code_cell(source="import time\ntime.sleep(2)"),
133+
])
134+
body, resources = export_through_preprocessor(
135+
notebook_node, ExecuteTimePreprocessor, NotebookExporter, 'ipynb')
136+
cells = json.loads(body)['cells']
137+
for cell in cells:
138+
if cell['cell_type'] != 'code':
139+
assert_not_in('ExecuteTime', cell['metadata'])
140+
else:
141+
assert_in('ExecuteTime', cell['metadata'])
142+
etmd = cell['metadata']['ExecuteTime']
143+
assert_in('start_time', etmd)
144+
assert_in('end_time', etmd)
145+
assert_greater_equal(
146+
_normalize_iso8601_timezone(etmd['end_time']),
147+
_normalize_iso8601_timezone(etmd['start_time']),
148+
'end_time should not be before start_time')

0 commit comments

Comments
 (0)