Skip to content

Commit

Permalink
Added postprocessing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dolsysmith committed Aug 24, 2023
1 parent 54fa918 commit 40c966e
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 869 deletions.
835 changes: 0 additions & 835 deletions course-utils/gw_bookstore_scraping.ipynb

This file was deleted.

Empty file added course_utils/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
60 changes: 41 additions & 19 deletions course-utils/postprocessing.py → course_utils/postprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ def __init__(self, nb_file):
:param nb_file: str or Path to an ipynb file.
'''
self.nb_json = self.load_nb(nb_file)
self.data = self.nb_json['cells']

def __iter__(self):
# implements iteration protocol
self.index = -1
return self

def __next__(self):
# iterates through notebook cells
if self.index < len(self.data) - 1:
self.index += 1
return self
else:
raise StopIteration

def hide_tags(self):
''''
Expand All @@ -47,9 +61,8 @@ def clear_outputs(self):
'''
Clears output on cells -- assuming the student notebooks should be clean of all outputs.
'''
for cell in self.nb_json['cells']:
if cell['cell_type'] == 'code':
cell['outputs'] = []
if self.data[self.index]['cell_type'] == 'code':
self.data[self.index]['outputs'] = []
return self

def make_glossary_links(self):
Expand All @@ -63,21 +76,30 @@ def term_expand(match_obj):
term = match_obj.group(1)
return f'[{term}]({GLOSSARY_URL}{term.replace(" ", "-")})'

for cell in self.nb_json['cells']:
if cell['cell_type'] == 'markdown':
for i, line in enumerate(cell['source']):
# Iterate over matches in line
new_line = re.sub(TERM_PATTERN, term_expand, line)
cell['source'][i] = new_line
if self.data[self.index]['cell_type'] == 'markdown':
for i, line in enumerate(self.data[self.index]['source']):
# Iterate over matches in line
new_line = re.sub(TERM_PATTERN, term_expand, line)
self.data[self.index]['source'][i] = new_line
return self

def ensure_hidden(self):
'''
Ensures that cells using the Exercise2 Jupyter Notebook extension are hidden by default.
'''
for cell in self.nb_json['cells']:
if 'solution2' in cell['metadata']:
cell['metadata']['solution2'] = 'hidden'
if 'solution2' in self.data[self.index]['metadata']:
self.data[self.index]['metadata']['solution2'] = 'hidden'
return self

def apply_hidden(self):
'''
Toggles the visibility of cells with the hide-cell tag.
'''
if 'hide-cell' in self.data[self.index]['metadata'].get('tags', []):
self.data[self.index]['metadata']['jupyter'] = {'source_hidden': True}
if self.data[self.index]['cell_type'] == 'code':
# Add comment that will be visible on toggled cell
self.data[self.index]['source'].insert(0, '#Click to see the solution.\n')
return self

def remove_directives(self):
Expand Down Expand Up @@ -115,18 +137,15 @@ def remove_tagged_cells(self, tags=TAGS_TO_REMOVE):
'''
:param tags: should be a Python set of tags. Any cells with any of these tags will be removed from the output notebook.
'''
output = []
for cell in self.nb_json['cells']:
cell_tags = cell['metadata'].get('tags', [])
if not (tags & set(cell_tags)):
output.append(deepcopy(cell))
self.nb_json['cells'] = output
self.data = [cell for cell in self.data
if not (tags & set(cell['metadata'].get('tags', [])))]
return self

def save_nb(self, nb_file):
'''
Saves notebook json at provided path
'''
self.nb_json['cells'] = self.data
with open(nb_file, 'w') as f:
json.dump(self.nb_json, f)
return self
Expand Down Expand Up @@ -160,7 +179,10 @@ def main(nb_input, nb_output):

logger.info(f'Processing notebook {in_}; saving output to {out}.')
nb = Notebook(in_)
nb.remove_directives().remove_tagged_cells().make_glossary_links().ensure_hidden().clear_outputs().hide_tags().save_nb(out)
nb.remove_tagged_cells()
for cell in nb:
cell.make_glossary_links().apply_hidden().clear_outputs()
nb.hide_tags().save_nb(out)

if __name__ == '__main__':
main()
4 changes: 2 additions & 2 deletions publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ function update_hw_modules() {
function main() {

echo "Building Parsons Problems"
python ./course-utils/parsons_builder.py
python ./course_utils/parsons_builder.py

echo "Building book from scratch"
jupyter-book clean textbook/
jupyter-book build textbook/

echo "Cleaning up notebook formatting"
python ./course-utils/postprocessing.py
python ./course_utils/postprocessing.py

echo "Publishing to GH pages"
ghp-import -n -p -f textbook/_build/html
Expand Down
Empty file added tests/__init__.py
Empty file.
122 changes: 122 additions & 0 deletions tests/test_postprocessing.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "39c26caf-db01-40d7-b62b-4973537b7b9c",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"clear-outputs"
]
},
"outputs": [
{
"data": {
"text/plain": [
"99.95"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# clear outputs\n",
"book_price = 99.95\n",
"num_students = 55\n",
"book_price"
]
},
{
"cell_type": "markdown",
"id": "ff62f5c5-3ad9-454c-ac42-7bd955e7c8d6",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"term-directive"
]
},
"source": [
"{term}`glossary term` should contain a link."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e41339fc-9bff-4560-b682-9169405f8199",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"hide-cell"
]
},
"outputs": [],
"source": [
"print(\"This cell should be hidden\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "53658e40-ffe4-4c73-9368-374d8cf54abb",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"remove-cell"
]
},
"outputs": [
{
"ename": "AssertionError",
"evalue": "This cell should have been removed.",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[3], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m#This cell should be removed.\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mThis cell should have been removed.\u001b[39m\u001b[38;5;124m'\u001b[39m\n",
"\u001b[0;31mAssertionError\u001b[0m: This cell should have been removed."
]
}
],
"source": [
"#This cell should be removed.\n",
"assert False, 'This cell should have been removed.'"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python Camp",
"language": "python",
"name": "env"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
26 changes: 26 additions & 0 deletions tests/test_postprocessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from course_utils.postprocessing import Notebook
import unittest
import json

class TestPostProcessing(unittest.TestCase):

def setUp(self):
self.notebook = Notebook('tests/test_postprocessing.ipynb')
self.notebook.remove_tagged_cells()
for cell in self.notebook:
cell.make_glossary_links().apply_hidden().clear_outputs()
self.notebook.hide_tags()

def testProcessing(self):

self.assertEqual(self.notebook.data[0]['outputs'], [],
'cell output not cleared')
self.assertRegex(self.notebook.data[1]['source'][0], r'\[.+\]\(https://gwu-libraries\.github.io/python-camp/glossary\.html#.+\)',
'Markdown link missing or malformed')
self.assertEqual(self.notebook.data[2]['source'][0], '#Click to see the solution.\n', 'Hidden code cell missing initial comment.')
self.assertEqual(self.notebook.data[2]['metadata']['jupyter'],
{"source_hidden": True},
'source_hidden flag missing from hidden cell metadata')
cells_for_removal = [cell for cell in self.notebook.data if 'remove-cell' in cell['metadata'].get('tags', [])]
self.assertEqual(cells_for_removal, [], 'cells tagged for removal not removed')

Loading

0 comments on commit 40c966e

Please sign in to comment.