Skip to content

Commit

Permalink
Merge pull request #640 from mpacer/tag_preprocessor
Browse files Browse the repository at this point in the history
Tag remove preprocessor
  • Loading branch information
minrk authored Aug 8, 2017
2 parents 8ee2194 + ff4d341 commit f3dee67
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 1 deletion.
1 change: 1 addition & 0 deletions nbconvert/exporters/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Exporter(LoggingConfigurable):
_preprocessors = List()

default_preprocessors = List([
'nbconvert.preprocessors.TagRemovePreprocessor',
'nbconvert.preprocessors.RegexRemovePreprocessor',
'nbconvert.preprocessors.ClearOutputPreprocessor',
'nbconvert.preprocessors.ExecutePreprocessor',
Expand Down
5 changes: 4 additions & 1 deletion nbconvert/exporters/templateexporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ def environment(self):
@property
def default_config(self):
c = Config({
'RegexRemovePreprocessor':{
'RegexRemovePreprocessor': {
'enabled': True
},
'TagRemovePreprocessor': {
'enabled': True
}
})
Expand Down
1 change: 1 addition & 0 deletions nbconvert/preprocessors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .clearoutput import ClearOutputPreprocessor
from .execute import ExecutePreprocessor
from .regexremove import RegexRemovePreprocessor
from .tagremove import TagRemovePreprocessor

# decorated function Preprocessors
from .coalescestreams import coalesce_streams
101 changes: 101 additions & 0 deletions nbconvert/preprocessors/tagremove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Module containing a preprocessor that removes cells if they match
one or more regular expression.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

from traitlets import Set, Unicode
from . import ClearOutputPreprocessor


class TagRemovePreprocessor(ClearOutputPreprocessor):
"""
Removes cells from a notebook that have tags that designate they are to be
removed prior to exporting the notebook.
Traitlets:
----------
remove_cell_tags: removes cells tagged with these values
remove_all_output_tags: removes entire output areas on cells
tagged with these values
remove_single_output_tags: removes individual output objects on
outputs tagged with these values
"""

remove_cell_tags = Set(Unicode, default_value=[],
help=("Tags indicating which cells are to be removed,"
"matches tags in `cell.metadata.tags`.")).tag(config=True)
remove_all_outputs_tags = Set(Unicode, default_value=[],
help=("Tags indicating cells for which the outputs are to be removed,"
"matches tags in `cell.metadata.tags`.")).tag(config=True)
remove_single_output_tags = Set(Unicode, default_value=[],
help=("Tags indicating which individual outputs are to be removed,"
"matches output *i* tags in `cell.outputs[i].metadata.tags`.")
).tag(config=True)

def check_cell_conditions(self, cell, resources, index):
"""
Checks that a cell has a tag that is to be removed
Returns: Boolean.
True means cell should *not* be removed.
"""

# Return true if any of the tags in the cell are removable.
return not self.remove_cell_tags.intersection(
cell.get('metadata', {}).get('tags', []))

def preprocess(self, nb, resources):
"""
Preprocessing to apply to each notebook. See base.py for details.
"""
# Skip preprocessing if the list of patterns is empty
if not any([self.remove_cell_tags,
self.remove_all_outputs_tags,
self.remove_single_output_tags]):
return nb, resources

# Filter out cells that meet the conditions
nb.cells = [self.preprocess_cell(cell, resources, index)[0]
for index, cell in enumerate(nb.cells)
if self.check_cell_conditions(cell, resources, index)]

return nb, resources

def preprocess_cell(self, cell, resources, cell_index):
"""
Apply a transformation on each cell. See base.py for details.
"""

if (self.remove_all_outputs_tags.intersection(
cell.get('metadata', {}).get('tags', []))
and cell.cell_type == 'code'):
cell.outputs = []
cell.execution_count = None
# Remove metadata associated with output
if 'metadata' in cell:
for field in self.remove_metadata_fields:
cell.metadata.pop(field, None)
if cell.get('outputs', []):
cell.outputs = [output
for output_index, output in enumerate(cell.outputs)
if self.check_output_conditions(output,
resources,
cell_index,
output_index)
]
return cell, resources

def check_output_conditions(self, output, resources,
cell_index, output_index):
"""
Checks that an output has a tag that indicates removal.
Returns: Boolean.
True means output should *not* be removed.
"""
return not self.remove_single_output_tags.intersection(
output.get('metadata', {}).get('tags', []))
83 changes: 83 additions & 0 deletions nbconvert/preprocessors/tests/test_tagremove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Module with tests for the TagRemovePreprocessor.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

from nbformat import v4 as nbformat

from .base import PreprocessorTestsBase
from ..tagremove import TagRemovePreprocessor


class TestTagRemove(PreprocessorTestsBase):
"""Contains test functions for tagremove.py"""

def build_notebook(self):
"""
Build a notebook to have metadata tags for cells, output_areas, and
individual outputs.
"""
notebook = super(TestTagRemove, self).build_notebook()
# Add a few empty cells
notebook.cells[0].outputs.extend(
[nbformat.new_output("display_data",
data={'text/plain': 'i'},
metadata={'tags': ["hide_one_output"]}
),
])
outputs_to_be_removed = [
nbformat.new_output("display_data",
data={'text/plain': "remove_my_output"}),
]
outputs_to_be_kept = [
nbformat.new_output("stream",
name="stdout",
text="remove_my_output",
),
]
notebook.cells.extend(
[nbformat.new_code_cell(source="display('remove_my_output')",
execution_count=2,
outputs=outputs_to_be_removed,
metadata={"tags": ["hide_all_outputs"]}),

nbformat.new_code_cell(source="print('remove this cell')",
execution_count=3,
outputs=outputs_to_be_kept,
metadata={"tags": ["hide_this_cell"]}),
]
)

return notebook

def build_preprocessor(self):
"""Make an instance of a preprocessor"""
preprocessor = TagRemovePreprocessor()
preprocessor.enabled = True
return preprocessor

def test_constructor(self):
"""Can a TagRemovePreprocessor be constructed?"""
self.build_preprocessor()

def test_output(self):
"""Test the output of the TagRemovePreprocessor"""
nb = self.build_notebook()
res = self.build_resources()
preprocessor = self.build_preprocessor()
preprocessor.remove_cell_tags.add("hide_this_cell")
preprocessor.remove_all_outputs_tags.add('hide_all_outputs')
preprocessor.remove_single_output_tags.add('hide_one_output')

nb, res = preprocessor(nb, res)

# checks that we can remove entire cells
self.assertEqual(len(nb.cells), 3)

# checks that we can remove output areas
self.assertEqual(len(nb.cells[-1].outputs), 0)

# checks that we can remove individual outputs
self.assertEqual(len(nb.cells[0].outputs), 8)

0 comments on commit f3dee67

Please sign in to comment.