Skip to content

Commit

Permalink
Introducing default_nbrmd_formats #12
Browse files Browse the repository at this point in the history
  • Loading branch information
mwouts committed Jul 16, 2018
1 parent b78554b commit 8c1908a
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 66 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ inputs.
## Can I save my Jupyter notebook as both R markdown and ipynb?

Yes. That's useful if you want to preserve the outputs locally, or if you want
to share the `.ipynb` version. We offer both per-notebook, and global configuration.
to share the `.ipynb` version. By default, the opened notebook in jupyter, plus
its `.ipynb` version, are updated when a notebook is saved.

If you prefer a different setting, we offer both per-notebook, and global configuration.

### Per-notebook configuration

Expand All @@ -93,12 +96,10 @@ Accepted formats are: `.ipynb`, `.Rmd` and `.md`.
If you want every notebook to be saved as both `.Rmd` and `.ipynb` files, then change your jupyter config to
```python
c.NotebookApp.contents_manager_class = 'nbrmd.RmdFileContentsManager'
c.ContentsManager.pre_save_hook = 'nbrmd.update_rmd_and_ipynb'
c.ContentsManager.default_nbrmd_formats = ['.ipynb', '.Rmd']
```

If you prefer to update just one of `.Rmd` or `.ipynb` files, then change the above to
`nbrmd.update_rmd` or `nbrmd.update_ipynb` as the `pre_save_hook` (and yes, you're free to use the `pre_save_hook`
with the default `ContentsManager`).
If you prefer to update just `.Rmd`, change the above accordingly.

:warning: Be careful not to open twice a notebook with two distinct extensions! You should _shutdown_ the notebooks
with the extension you are not currently editing (list your open notebooks with the _running_ tab in Jupyter).
Expand Down
1 change: 0 additions & 1 deletion nbrmd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"""

from .nbrmd import readf, writef, writes, reads, notebook_extensions, readme
from .hooks import *

try:
from .rmarkdownexporter import RMarkdownExporter
Expand Down
14 changes: 9 additions & 5 deletions nbrmd/cm.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import notebook.transutils
from notebook.services.contents.filemanager import FileContentsManager
from tornado.web import HTTPError
from .combine import combine_inputs_with_outputs
from .hooks import update_selected_formats
import hooks
import combine

import os
import nbrmd
Expand Down Expand Up @@ -30,14 +30,17 @@ class RmdFileContentsManager(FileContentsManager):
Jupyter notebooks (.ipynb), or in R Markdown (.Rmd), plain markdown
(.md), R scripts (.R) or python scripts (.py)
"""

nb_extensions = [ext for ext in nbrmd.notebook_extensions if
ext != '.ipynb']

def all_nb_extensions(self):
return ['.ipynb'] + self.nb_extensions

default_nbrmd_formats = ['.ipynb']

def __init__(self, **kwargs):
self.pre_save_hook = update_selected_formats
self.pre_save_hook = hooks.update_alternative_formats
super(RmdFileContentsManager, self).__init__(**kwargs)

def _read_notebook(self, os_path, as_version=4):
Expand Down Expand Up @@ -81,8 +84,9 @@ def get(self, path, content=True, type=None, format=None):
try:
nb_outputs = self._notebook_model(
path_ipynb, content=content)
combine_inputs_with_outputs(nb['content'],
nb_outputs['content'])
combine.combine_inputs_with_outputs(
nb['content'],
nb_outputs['content'])
except HTTPError:
pass

Expand Down
59 changes: 14 additions & 45 deletions nbrmd/hooks.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,18 @@
import os
import nbrmd
import nbformat
import cm


def check_extensions(extensions):
if extensions is None:
extensions = []
if isinstance(extensions, str):
extensions = [extensions]
if not isinstance(extensions, list) or not set(extensions).issubset(
nbrmd.notebook_extensions):
raise TypeError('Notebook extensions '
'should be a subset of {},'
'but are {}'.format(str(nbrmd.notebook_extensions),
str(extensions)))
return extensions


def update_formats(extensions=None):
"""A function that generates a pre_save_hook for the desired extensions"""
extensions = check_extensions(extensions)

def pre_save_hook(model, path, contents_manager=None, **kwargs):
return update_selected_formats(model, path,
contents_manager,
extensions=extensions, **kwargs)

return pre_save_hook


def update_selected_formats(model, path, contents_manager=None,
extensions=None, **kwargs):
def update_alternative_formats(model, path, contents_manager=None, **kwargs):
"""
A pre-save hook for jupyter that saves notebooks to multiple files
with the desired extensions.
A pre-save hook for jupyter that saves the notebooks
under the alternative form. Target extensions are taken from
notebook metadata 'nbrmd_formats', or when not available,
from contents_manager.default_nbrmd_formats
:param model: data model, that may contain the notebook
:param path: full name for ipython notebook
:param contents_manager: ContentsManager instance
:param extensions: list of alternative formats
:param kwargs: not used
:return:
"""
Expand All @@ -51,28 +26,22 @@ def update_selected_formats(model, path, contents_manager=None,
if nb['nbformat'] != 4:
return

extensions = check_extensions(extensions)
extensions = (nb.get('metadata', {}
).get('nbrmd_formats', extensions)
or extensions)
formats = contents_manager.default_nbrmd_formats \
if isinstance(contents_manager, cm.RmdFileContentsManager) else ['.ipynb']
formats = nb.get('metadata', {}).get('nbrmd_formats', formats)
if not isinstance(formats, list) or not set(formats).issubset(
['.Rmd', '.md', '.ipynb']):
raise TypeError(u"Notebook metadata 'nbrmd_formats' "
u"should be subset of ['.Rmd', '.md', '.ipynb']")

os_path = contents_manager._get_os_path(path) if contents_manager else path
file, ext = os.path.splitext(path)
os_file, ext = os.path.splitext(os_path)

for alt_ext in extensions:
for alt_ext in formats:
if ext != alt_ext:
if contents_manager:
contents_manager.log.info(
u"Saving file at /%s", file + alt_ext)
nbrmd.writef(nbformat.notebooknode.from_dict(nb),
os_file + alt_ext)


update_rmd_and_ipynb = update_formats(['.ipynb', '.Rmd'])
update_ipynb = update_formats('.ipynb')
update_rmd = update_formats('.Rmd')
update_md = update_formats('.md')
update_py = update_formats('.py')
update_py_and_ipynb = update_formats(['.ipynb', '.py'])
update_R = update_formats('.R')
27 changes: 17 additions & 10 deletions tests/test_jupyter_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ def test_rmd_is_ok(nb_file, tmpdir):
tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
tmp_rmd = str(tmpdir.join('notebook.Rmd'))

nbrmd.update_rmd(model=dict(type='notebook', content=nb), path=tmp_ipynb)
nb.metadata['nbrmd_formats'] = ['.Rmd']
nbrmd.update_alternative_formats(
model=dict(type='notebook', content=nb),
path=tmp_ipynb)

nb2 = nbrmd.readf(tmp_rmd)

Expand All @@ -24,7 +27,9 @@ def test_ipynb_is_ok(nb_file, tmpdir):
tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
tmp_rmd = str(tmpdir.join('notebook.Rmd'))

nbrmd.update_ipynb(model=dict(type='notebook', content=nb), path=tmp_rmd)
nbrmd.update_alternative_formats(
model=dict(type='notebook', content=nb),
path=tmp_rmd)

nb2 = nbrmd.readf(tmp_ipynb)

Expand All @@ -39,8 +44,9 @@ def test_all_files_created(nb_file, tmpdir):
tmp_rmd = str(tmpdir.join('notebook.Rmd'))
nb.metadata['nbrmd_formats'] = ['.Rmd', '.ipynb', '.md']

nbrmd.update_selected_formats(
model=dict(type='notebook', content=nb), path=tmp_ipynb)
nbrmd.update_alternative_formats(
model=dict(type='notebook', content=nb),
path=tmp_ipynb)

nb2 = nbrmd.readf(tmp_md)
assert remove_outputs_and_header(nb) == remove_outputs_and_header(nb2)
Expand All @@ -54,7 +60,7 @@ def test_no_files_created_on_no_format(tmpdir):
tmp_md = str(tmpdir.join('notebook.md'))
tmp_rmd = str(tmpdir.join('notebook.Rmd'))

nbrmd.update_selected_formats(
nbrmd.update_alternative_formats(
model=dict(type='notebook',
content=dict(nbformat=4, metadata=dict())),
path=tmp_ipynb)
Expand All @@ -67,7 +73,7 @@ def test_raise_on_wrong_format(tmpdir):
tmp_ipynb = str(tmpdir.join('notebook.ipynb'))

with pytest.raises(TypeError):
nbrmd.update_selected_formats(
nbrmd.update_alternative_formats(
model=dict(type='notebook',
content=dict(nbformat=4,
metadata=dict(nbrmd_formats=['.doc']))),
Expand All @@ -78,15 +84,16 @@ def test_no_rmd_on_not_notebook(tmpdir):
tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
tmp_rmd = str(tmpdir.join('notebook.Rmd'))

nbrmd.update_rmd(model=dict(type='not notebook'), path=tmp_ipynb)
nbrmd.update_alternative_formats(model=dict(type='not notebook'), path=tmp_ipynb)
assert not os.path.isfile(tmp_rmd)


def test_no_rmd_on_not_v4(tmpdir):
tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
tmp_rmd = str(tmpdir.join('notebook.Rmd'))

nbrmd.update_rmd(
model=dict(type='notebook', content=dict(nbformat=3)), path=tmp_ipynb)
nbrmd.update_alternative_formats(
model=dict(type='notebook', content=dict(nbformat=3)),
path=tmp_rmd)

assert not os.path.isfile(tmp_rmd)
assert not os.path.isfile(tmp_ipynb)

0 comments on commit 8c1908a

Please sign in to comment.