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

Tag remove preprocessor #640

Merged
merged 8 commits into from
Aug 8, 2017
Merged
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)