Skip to content

Commit 594df54

Browse files
authored
Merge pull request #131 from man-group/add-reveal-js
Adding support for Reveal.js slideshows (#113)
2 parents ce4fd0b + e2c5a1a commit 594df54

File tree

18 files changed

+191
-75
lines changed

18 files changed

+191
-75
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
0.4.6 (2022-??-??)
1+
0.5.0 (2023-??-??)
22
------------------
33

4+
* Feature: Added support for [Reveal.js](https://revealjs.com/) notebook outputs
45
* Bugfix: Small bugfix for synchronous report execution
56
* Improvement: Delete functionality in mongo now also deletes files from GridFS
67

7-
88
0.4.5 (2022-09-29)
99
------------------
1010

notebooker/_entrypoints.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,10 @@ def filesystem_default_value(dirname):
6464
help="If selected, notebooker will not try to pull the latest version of python templates from git.",
6565
)
6666
@click.option(
67-
"--default-mailfrom",
68-
default=DEFAULT_MAILFROM_ADDRESS,
69-
help="Set a new value for the default mailfrom setting."
67+
"--default-mailfrom", default=DEFAULT_MAILFROM_ADDRESS, help="Set a new value for the default mailfrom setting."
7068
)
7169
@click.option(
72-
"--running-timeout",
73-
default=DEFAULT_RUNNING_TIMEOUT,
74-
help="Timeout in minutes for report execution",
75-
type=int
70+
"--running-timeout", default=DEFAULT_RUNNING_TIMEOUT, help="Timeout in minutes for report execution", type=int
7671
)
7772
@click.option(
7873
"--serializer-cls",
@@ -207,6 +202,12 @@ def start_webapp(
207202
default=None,
208203
help="Use this email in the From header of any sent email. If not passed, --default-mailfrom will be used",
209204
)
205+
@click.option(
206+
"--is-slideshow",
207+
default=False,
208+
is_flag=True,
209+
help="If specified, the notebook template's output will be treated as a Reveal.js slideshow.",
210+
)
210211
@pass_config
211212
def execute_notebook(
212213
config: BaseConfig,
@@ -224,6 +225,7 @@ def execute_notebook(
224225
prepare_notebook_only,
225226
scheduler_job_id,
226227
mailfrom,
228+
is_slideshow,
227229
):
228230
if report_name is None:
229231
raise ValueError("Error! Please provide a --report-name.")
@@ -243,6 +245,7 @@ def execute_notebook(
243245
prepare_notebook_only,
244246
scheduler_job_id,
245247
mailfrom,
248+
is_slideshow=is_slideshow,
246249
)
247250

248251

notebooker/constants.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class NotebookResultBase(object):
8585
stdout = attr.ib(default=attr.Factory(list))
8686
scheduler_job_id = attr.ib(default=None)
8787
mailfrom = attr.ib(default=None)
88+
is_slideshow = attr.ib(default=False)
8889

8990
def saveable_output(self):
9091
out = attr.asdict(self)
@@ -103,6 +104,7 @@ class NotebookResultPending(NotebookResultBase):
103104
hide_code = attr.ib(default=False)
104105
scheduler_job_id = attr.ib(default=None)
105106
mailfrom = attr.ib(default=None)
107+
is_slideshow = attr.ib(default=False)
106108

107109

108110
@attr.s()
@@ -117,6 +119,7 @@ class NotebookResultError(NotebookResultBase):
117119
hide_code = attr.ib(default=False)
118120
scheduler_job_id = attr.ib(default=None)
119121
mailfrom = attr.ib(default=None)
122+
is_slideshow = attr.ib(default=False)
120123

121124
@property
122125
def email_subject(self):
@@ -158,6 +161,7 @@ class NotebookResultComplete(NotebookResultBase):
158161
stdout = attr.ib(default=attr.Factory(list))
159162
scheduler_job_id = attr.ib(default=None)
160163
mailfrom = attr.ib(default=None)
164+
is_slideshow = attr.ib(default=False)
161165

162166
def html_resources(self):
163167
"""We have to save the raw images using Mongo GridFS - figure out where they will go here"""
@@ -189,6 +193,7 @@ def saveable_output(self):
189193
"scheduler_job_id": self.scheduler_job_id,
190194
"raw_html": "", # backwards compatibility for versions<0.3.1
191195
"mailfrom": self.mailfrom,
196+
"is_slideshow": self.is_slideshow,
192197
}
193198

194199
def __repr__(self):
@@ -197,7 +202,7 @@ def __repr__(self):
197202
"job_start_time={job_start_time}, job_finish_time={job_finish_time}, update_time={update_time}, "
198203
"report_title={report_title}, overrides={overrides}, mailto={mailto}, mailfrom={mailfrom}"
199204
"email_subject={email_subject}, generate_pdf_output={generate_pdf_output}, hide_code={hide_code}, "
200-
"scheduler_job_id={scheduler_job_id})".format(
205+
"scheduler_job_id={scheduler_job_id}, is_slideshow={is_slideshow})".format(
201206
job_id=self.job_id,
202207
status=self.status,
203208
report_name=self.report_name,
@@ -212,5 +217,6 @@ def __repr__(self):
212217
generate_pdf_output=self.generate_pdf_output,
213218
hide_code=self.hide_code,
214219
scheduler_job_id=self.scheduler_job_id,
220+
is_slideshow=self.is_slideshow,
215221
)
216222
)

notebooker/execute_notebook.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _run_checks(
4646
py_template_subdir: str = "",
4747
scheduler_job_id: Optional[str] = None,
4848
mailfrom: Optional[str] = None,
49+
is_slideshow: bool = False,
4950
) -> NotebookResultComplete:
5051
"""
5152
This is the actual method which executes a notebook, whether running in the webapp or via the entrypoint.
@@ -77,7 +78,8 @@ def _run_checks(
7778
If available, it will be part of the Error or Completed run report.
7879
mailfrom : `Optional[str]`
7980
If available, this will be the email used in the From header.
80-
81+
is_slideshow: bool
82+
Whether or not the output of this should use the equivalent of nbconvert --to slides
8183
8284
Returns
8385
-------
@@ -102,14 +104,18 @@ def _run_checks(
102104

103105
logger.info("Executing notebook at {} using parameters {} --> {}".format(ipynb_raw_path, overrides, output_ipynb))
104106
pm.execute_notebook(
105-
ipynb_raw_path, ipynb_executed_path, parameters=overrides, log_output=True, prepare_only=prepare_only
107+
ipynb_raw_path,
108+
ipynb_executed_path,
109+
parameters=overrides,
110+
log_output=True,
111+
prepare_only=prepare_only,
106112
)
107113
with open(ipynb_executed_path, "r") as f:
108114
raw_executed_ipynb = f.read()
109115

110116
logger.info("Saving output notebook as HTML from {}".format(ipynb_executed_path))
111-
html, resources = ipython_to_html(ipynb_executed_path, job_id)
112-
email_html, _ = ipython_to_html(ipynb_executed_path, job_id, hide_code=hide_code)
117+
html, resources = ipython_to_html(ipynb_executed_path, job_id, is_slideshow=is_slideshow)
118+
email_html, _ = ipython_to_html(ipynb_executed_path, job_id, hide_code=hide_code, is_slideshow=is_slideshow)
113119
pdf = ipython_to_pdf(raw_executed_ipynb, report_title, hide_code=hide_code) if generate_pdf_output else ""
114120

115121
notebook_result = NotebookResultComplete(
@@ -129,6 +135,7 @@ def _run_checks(
129135
overrides=overrides,
130136
scheduler_job_id=scheduler_job_id,
131137
mailfrom=mailfrom,
138+
is_slideshow=is_slideshow,
132139
)
133140
return notebook_result
134141

@@ -154,12 +161,17 @@ def run_report(
154161
py_template_subdir="",
155162
scheduler_job_id=None,
156163
mailfrom=None,
164+
is_slideshow=False,
157165
):
158166

159167
job_id = job_id or str(uuid.uuid4())
160168
stop_execution = os.getenv("NOTEBOOKER_APP_STOPPING")
161169
if stop_execution:
162-
logger.info("Aborting attempt to run %s, jobid=%s as app is shutting down.", report_name, job_id)
170+
logger.info(
171+
"Aborting attempt to run %s, jobid=%s as app is shutting down.",
172+
report_name,
173+
job_id,
174+
)
163175
result_serializer.update_check_status(job_id, JobStatus.CANCELLED, error_info=CANCEL_MESSAGE)
164176
return
165177
try:
@@ -170,7 +182,10 @@ def run_report(
170182
attempts_remaining,
171183
)
172184
result_serializer.update_check_status(
173-
job_id, report_name=report_name, job_start_time=job_submit_time, status=JobStatus.PENDING
185+
job_id,
186+
report_name=report_name,
187+
job_start_time=job_submit_time,
188+
status=JobStatus.PENDING,
174189
)
175190
result = _run_checks(
176191
job_id,
@@ -190,6 +205,7 @@ def run_report(
190205
py_template_subdir=py_template_subdir,
191206
scheduler_job_id=scheduler_job_id,
192207
mailfrom=mailfrom,
208+
is_slideshow=is_slideshow,
193209
)
194210
logger.info("Successfully got result.")
195211
result_serializer.save_check_result(result)
@@ -208,6 +224,7 @@ def run_report(
208224
generate_pdf_output=generate_pdf_output,
209225
scheduler_job_id=scheduler_job_id,
210226
mailfrom=mailfrom,
227+
is_slideshow=is_slideshow,
211228
)
212229
logger.error(
213230
"Report run failed. Saving error result to mongo library %s@%s...",
@@ -239,6 +256,7 @@ def run_report(
239256
py_template_subdir=py_template_subdir,
240257
scheduler_job_id=scheduler_job_id,
241258
mailfrom=mailfrom,
259+
is_slideshow=is_slideshow,
242260
)
243261
else:
244262
logger.info("Abandoning attempt to run report. It failed too many times.")
@@ -327,6 +345,7 @@ def execute_notebook_entrypoint(
327345
prepare_notebook_only: bool,
328346
scheduler_job_id: Optional[str],
329347
mailfrom: Optional[str],
348+
is_slideshow: bool,
330349
):
331350
report_title = report_title or report_name
332351
output_dir, template_dir, _ = initialise_base_dirs(output_dir=config.OUTPUT_DIR, template_dir=config.TEMPLATE_DIR)
@@ -351,6 +370,7 @@ def execute_notebook_entrypoint(
351370
logger.info("mailfrom = %s" % mailfrom)
352371
logger.info("pdf_output = %s", pdf_output)
353372
logger.info("hide_code = %s", hide_code)
373+
logger.info("is_slideshow = %s", is_slideshow)
354374
logger.info("prepare_notebook_only = %s", prepare_notebook_only)
355375
logger.info("scheduler job id = %s", scheduler_job_id)
356376
logger.info("notebooker_disable_git = %s", notebooker_disable_git)
@@ -384,6 +404,7 @@ def execute_notebook_entrypoint(
384404
py_template_subdir=py_template_subdir,
385405
scheduler_job_id=scheduler_job_id,
386406
mailfrom=mailfrom,
407+
is_slideshow=is_slideshow,
387408
)
388409
if result.mailto:
389410
send_result_email(result, config.DEFAULT_MAILFROM)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# text_representation:
5+
# extension: .py
6+
# format_name: light
7+
# format_version: '1.5'
8+
# jupytext_version: 1.3.0
9+
# kernelspec:
10+
# display_name: py3-local-pegasus
11+
# language: python
12+
# name: env
13+
# ---
14+
15+
# + tags=["parameters"]
16+
the_range = 10
17+
# -
18+
19+
# + [markdown] slideshow={"slide_type": "slide"}
20+
# # This is slide number one
21+
22+
# + [markdown] slideshow={"slide_type": "subslide"}
23+
# ## This is slide two
24+
#
25+
# - I have some
26+
# - things to talk
27+
# - about.
28+
29+
# + [markdown] slideshow={"slide_type": "slide"}
30+
# # Okay onto the good stuff!
31+
# -
32+
33+
list(range(the_range))

notebooker/serialization/mongo.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import datetime
22
import json
3-
from collections import Counter, defaultdict
4-
5-
from abc import ABC
3+
from collections import defaultdict
64
from logging import getLogger
75
from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union, Iterator
86

97
import click
108
import gridfs
119
import pymongo
10+
from abc import ABC
1211
from gridfs import NoFile
1312

1413
from notebooker.constants import JobStatus, NotebookResultComplete, NotebookResultError, NotebookResultPending
@@ -53,7 +52,6 @@ def read_bytes_file(result_data_store, path):
5352

5453

5554
def load_files_from_gridfs(result_data_store: gridfs.GridFS, result: Dict, do_read=True) -> List[str]:
56-
5755
gridfs_filenames = []
5856
all_html_output_paths = result.get("raw_html_resources", {}).get("outputs", [])
5957
gridfs_filenames.extend(all_html_output_paths)
@@ -89,7 +87,6 @@ def load_files_from_gridfs(result_data_store: gridfs.GridFS, result: Dict, do_re
8987

9088
class MongoResultSerializer(ABC):
9189
# This class is the interface between Mongo and the rest of the application
92-
9390
def __init__(self, database_name="notebooker", mongo_host="localhost", result_collection_name="NOTEBOOK_OUTPUT"):
9491
self.database_name = database_name
9592
self.mongo_host = mongo_host
@@ -193,6 +190,7 @@ def save_check_stub(
193190
generate_pdf_output: bool = True,
194191
hide_code: bool = False,
195192
scheduler_job_id: Optional[str] = None,
193+
is_slideshow: bool = False,
196194
) -> None:
197195
"""Call this when we are just starting a check. Saves a "pending" job into storage."""
198196
job_start_time = job_start_time or datetime.datetime.now()
@@ -208,6 +206,7 @@ def save_check_stub(
208206
overrides=overrides or {},
209207
hide_code=hide_code,
210208
scheduler_job_id=scheduler_job_id,
209+
is_slideshow=is_slideshow,
211210
)
212211
self._save_to_db(pending_result)
213212

@@ -303,6 +302,7 @@ def _convert_result(
303302
hide_code=result.get("hide_code", False),
304303
stdout=result.get("stdout", []),
305304
scheduler_job_id=result.get("scheduler_job_id", None),
305+
is_slideshow=result.get("is_slideshow", False),
306306
)
307307
elif cls == NotebookResultPending:
308308
return NotebookResultPending(
@@ -318,6 +318,7 @@ def _convert_result(
318318
hide_code=result.get("hide_code", False),
319319
stdout=result.get("stdout", []),
320320
scheduler_job_id=result.get("scheduler_job_id", None),
321+
is_slideshow=result.get("is_slideshow", False),
321322
)
322323

323324
elif cls == NotebookResultError:
@@ -340,6 +341,7 @@ def _convert_result(
340341
hide_code=result.get("hide_code", False),
341342
stdout=result.get("stdout", []),
342343
scheduler_job_id=result.get("scheduler_job_id", False),
344+
is_slideshow=result.get("is_slideshow", False),
343345
)
344346
else:
345347
raise ValueError("Could not deserialise {} into result object.".format(result))

0 commit comments

Comments
 (0)