Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ExecuteTime] add preprocessor to execute notebook updating timing metadata #1139

Merged
merged 4 commits into from
Nov 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/exporting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Generic documentation for preprocessors can be found at
`nbconvert.readthedocs.io/en/latest/api/preprocessors.html <http://nbconvert.readthedocs.io/en/latest/api/preprocessors.html>`__.


Executing and updating timing metadata
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. autoclass:: ExecuteTimePreprocessor


Retaining Codefolding
^^^^^^^^^^^^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .collapsible_headings import ExporterCollapsibleHeadings
from .embedhtml import EmbedHTMLExporter
from .execute_time import ExecuteTimePreprocessor
from .exporter_inliner import ExporterInliner
from .nbTranslate import NotebookLangExporter
from .pp_highlighter import HighlighterPostProcessor, HighlighterPreprocessor
Expand All @@ -16,6 +17,7 @@
'templates_directory',
'CodeFoldingPreprocessor',
'EmbedHTMLExporter',
'ExecuteTimePreprocessor',
'ExporterCollapsibleHeadings',
'ExporterInliner',
'HighlighterPostProcessor',
Expand Down
40 changes: 40 additions & 0 deletions src/jupyter_contrib_nbextensions/nbconvert_support/execute_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Module containing a preprocessor tto execute code cells, updating time metadata
"""

from datetime import datetime

from nbconvert.preprocessors.execute import ExecutePreprocessor

try:
# notebook >= 5.0.0-rc1
import notebook._tz as nbtz
except ImportError:
# notebook < 5.0.0-rc1
import notebook.services.contents.tz as nbtz


class ExecuteTimePreprocessor(ExecutePreprocessor):
"""
Executes all the cells in a notebook, updating their ExecuteTime metadata.
"""

def run_cell(self, cell, cell_index, *args, **kwargs):
before = datetime.utcnow()
exec_reply, outs = super(ExecuteTimePreprocessor, self).run_cell(
cell, cell_index, *args, **kwargs)

if exec_reply.get('msg_type', '') == 'execute_reply':
ets = cell.setdefault('metadata', {}).setdefault('ExecuteTime', {})
if 'started' in exec_reply.get('metadata', {}):
# started value should is already a string, so don't isoformat
ets['start_time'] = exec_reply['metadata']['started']
else:
# attempt to fallback to datetime obj for execution request msg
ets['start_time'] = exec_reply.get(
'parent_header', {}).get('date', before).isoformat()
ets['end_time'] = (
exec_reply.get('header', {}).get('date') or nbtz.utcnow()
).isoformat()

return exec_reply, outs
43 changes: 41 additions & 2 deletions tests/test_preprocessors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-

import json
import os
import re

import nbformat.v4 as nbf
from nbconvert import LatexExporter, RSTExporter
from nbconvert import LatexExporter, NotebookExporter, RSTExporter
from nbconvert.utils.pandoc import PandocMissing
from nose.plugins.skip import SkipTest
from nose.tools import assert_in, assert_not_in, assert_true
from nose.tools import (
assert_greater_equal, assert_in, assert_not_in, assert_true,
)
from traitlets.config import Config


Expand Down Expand Up @@ -107,3 +111,38 @@ def test_preprocessor_svg2pdf():
assert_true(pdf_existed, 'exported pdf should exist')
assert_in('test.pdf', body,
'exported pdf should be referenced in exported notebook')


def _normalize_iso8601_timezone(timestamp_str):
# Zulu -> +00:00 offset
timestamp_str = re.sub(r'Z$', r'+00:00', timestamp_str)
# HH -> HH:00 offset
timestamp_str = re.sub(r'([+-]\d\d)$', r'\1:00', timestamp_str)
# HHMM -> HH:MM offset
timestamp_str = re.sub(r'([+-]\d\d):?(\d\d)$', r'\1:\2', timestamp_str)
return timestamp_str


def test_preprocessor_execute_time():
"""Test ExecuteTime preprocessor."""
# check import shortcut
from jupyter_contrib_nbextensions.nbconvert_support import ExecuteTimePreprocessor # noqa E501
notebook_node = nbf.new_notebook(cells=[
nbf.new_code_cell(source="a = 'world'"),
nbf.new_code_cell(source="import time\ntime.sleep(2)"),
])
body, resources = export_through_preprocessor(
notebook_node, ExecuteTimePreprocessor, NotebookExporter, 'ipynb')
cells = json.loads(body)['cells']
for cell in cells:
if cell['cell_type'] != 'code':
assert_not_in('ExecuteTime', cell['metadata'])
else:
assert_in('ExecuteTime', cell['metadata'])
etmd = cell['metadata']['ExecuteTime']
assert_in('start_time', etmd)
assert_in('end_time', etmd)
assert_greater_equal(
_normalize_iso8601_timezone(etmd['end_time']),
_normalize_iso8601_timezone(etmd['start_time']),
'end_time should not be before start_time')