Skip to content
This repository has been archived by the owner on Apr 4, 2019. It is now read-only.

Commit

Permalink
Merge pull request #4 from mwouts/v0.4.3
Browse files Browse the repository at this point in the history
V0.4.3
  • Loading branch information
mwouts authored Jul 26, 2018
2 parents 58dd56d + a9a4650 commit d32f7cf
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 57 deletions.
8 changes: 8 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ Release History
dev
+++

0.4.3 (2018-07-25)
+++++++++++++++++++

**Improvements**

- Readme refactored, notebook demos available on binder #23
- Based on nbrmd 0.4.3 (multiline comments now supported)

0.4.0 (2018-07-18)
+++++++++++++++++++

Expand Down
98 changes: 66 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
# Python and R scripts as Jupyter notebooks
# Jupyter notebooks from/to Python or R scripts

[![Pypi](https://img.shields.io/pypi/v/nbsrc.svg)](https://pypi.python.org/pypi/nbsrc)
[![Pypi](https://img.shields.io/pypi/l/nbsrc.svg)](https://pypi.python.org/pypi/nbsrc)
[![Build Status](https://travis-ci.com/mwouts/nbsrc.svg?branch=master)](https://travis-ci.com/mwouts/nbsrc)
[![codecov.io](https://codecov.io/github/mwouts/nbsrc/coverage.svg?branch=master)](https://codecov.io/github/mwouts/nbsrc?branch=master)
![pylint Score](https://mperlet.github.io/pybadge/badges/9.6.svg)
[![pyversions](https://img.shields.io/pypi/pyversions/nbsrc.svg)](https://pypi.python.org/pypi/nbsrc)
[![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/mwouts/nbrmd/master?filepath=demo)

This package provides companion scripts (`.py` or `.R` extension)
to your Jupyter notebooks, that are always *synchronized*
with the notebook. With this you will be able to
- set the `.py` or `.R` script under version control
- modify the script outside of Jupyter, and easily merge multiple contributions
to the notebook using standard, text merge tools
- reload the latest version of the notebook from the `.py` or `.R` script.
Outputs for the cells with unchanged input are taken from the `.ipynb` file.
Jupyter notebooks are complex files, that contain source code, metadata, and
rich outputs. Here we offer a simple and complementary format for Jupyter
notebooks, as pure python (or R) companion scripts.

The resulting python scripts are perfect candidates for
keeping notebooks under version control. They can be
edited outside of Jupyter, using
your favorite text editor, or even standard merge tools if you wish to merge
multiple contributions to a notebook.

With the `nbsrc` package, any python or R script can be loaded as a notebook
in Jupyter. If a classical `ipynb` notebook with a matching name exists,
outputs for matching inputs are reconstructed. And, if you associate python
and jupyter files as recommended below, when a `ipynb` notebook opens, the
corresponding inputs are taken from the `py` file, which you may have updated
outside of Jupyter.

## Can I have a demo?

Sure. Try our package on [binder](https://mybinder.org/v2/gh/mwouts/nbrmd/master?filepath=demo)!
There, you will be able
- to open and execute arbitrary python files as notebooks (give a try to
the matplotlib demo named `filled_step.py`)
- to open a notebook, then edit the companion python script, and reload the notebook,
to find up-to-date inputs in Jupyter.

## How does the python version look like?

Below is an example of a Jupyter notebook, together with its python representation.

We have hundreds of tests that ensure that
- Round trip conversion: python to notebook to python, is identity
- Round trip conversion, starting from a Jupyter notebook, preserves source
and metadata, not outputs. In some occasions (consecutive blank lines in
code cells), cells may be splitted into smaller ones.

Python [notebook](https://mybinder.org/v2/gh/mwouts/nbrmd/master?filepath=tests/python_notebook_sample.py) in Jupyter | Python [script](https://github.com/mwouts/nbrmd/blob/master/tests/python_notebook_sample.py)
:--------------------------:|:-----------------------:
![](https://raw.githubusercontent.com/mwouts/nbsrc/master/img/python_notebook.png) | ![](https://raw.githubusercontent.com/mwouts/nbsrc/master/img/python_source.png)

The representation of notebooks as R scripts follows the [standard](https://rmarkdown.rstudio.com/articles_report_from_r_script.html) for that language.

## How do I activate the companion script?

Expand All @@ -24,12 +59,25 @@ c.NotebookApp.contents_manager_class = 'nbrmd.RmdFileContentsManager'
c.ContentsManager.default_nbrmd_formats = 'ipynb,py'
```

Then, make sure you have the `nbrmd` packages installed, and re-start jupyter, i.e. run
Then, make sure you have the [`nbrmd`](https://github.com/mwouts/nbrmd)
package up-to-date, and re-start jupyter, i.e. run
```bash
pip install nbrmd --upgrade
jupyter notebook
```

With the above configuration, every Jupyter notebook will have a companion
`.py` script. And every `.py` script that you edit in Jupyter
will have a companion `.ipynb` notebook.

If you prefer the `.ipynb` notebook not to be created by Jupyter when a `.py`
script is edited, set
```
c.ContentsManager.default_nbrmd_formats = ''
```
(as the default value is `ipynb`). Outputs for scripts, however,
will not be saved any more.

## Per notebook configuration

With the above configuration, every notebook will have a companion `.py` file.
Expand All @@ -56,25 +104,16 @@ In case you want both `.py` and `.Rmd`, please note that the
order matters: the first non-`.ipynb` extension
is the one used as the reference source for notebook inputs.

## Can I edit the python file?

Yes, please! That's the precise purpose for the `nbsrc` package. When you're done, please _reload_ the notebook, i.e. refresh your notebook in the browser. Note that the url should have only the notebook name (no additional #), like
`http://localhost:8888/notebooks/GitHub/nbrmd/tests/python_notebook_sample.ipynb`.

As mentioned above, reloading the `.ipynb` with actually load updated inputs from the python script.

It is not required to _restart_ the current kernel. Reloading may remove a few outputs (those corresponding to inputs you have changed), but it will preserve the python variables.

Python notebook in Jupyter | Python script
:--------------------------:|:-----------------------:
![](https://raw.githubusercontent.com/mwouts/nbsrc/master/img/python_notebook.png) | ![](https://raw.githubusercontent.com/mwouts/nbsrc/master/img/python_source.png)

## What is the difference between `nbsrc` and `nbrmd`?

## How do you represent notebooks as scripts?
[`nbrmd`](https://github.com/mwouts/nbrmd)
is a python package that represents Jupyter notebooks as R markdown
files. It is also where notebooks as python scripts are implemented. But
I felt notebooks as scripts deserved a standalone documentation, and
that's the main reason for having the `nbsrc` package.

`.R` scripts follow the [standard](https://rmarkdown.rstudio.com/articles_report_from_r_script.html) for that language.

Designing a comfortable standard for `.py` scripts is not trivial. The current format is documented [here](https://github.com/mwouts/nbrmd/blob/master/tests/python_notebook_sample.py).
You don't actually need the `nbsrc` package unless you want the command line
conversion tools.

## Command line conversion

Expand All @@ -100,8 +139,3 @@ nbconvert jupyter.ipynb --to pynotebook
nbconvert jupyter.ipynb --to rnotebook
```

## And if I convert twice?

Round trip conversion of scripts is identity.
Round trip conversion of Jupyter notebooks preserves the source, not outputs.

12 changes: 6 additions & 6 deletions nbsrc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Jupyter notebooks as python or R script
Use this module to read or write Jupyter notebooks as R or python scripts
(methods 'read', 'reads', 'write', 'writes')
Use the 'nbsrc' conversion script to convert Jupyter notebooks from/to
R or Python scripts
NB: read, write methods, as well as ContentsManager, are to be found in the
nbrmd package
"""

from .nbsrc import readme

try:
from .srcexporter import PyNotebookExporter
from .srcexporter import RNotebookExporter
except ImportError as e:
PyNotebookExporter = str(e)
RNotebookExporter = str(e)
except ImportError as error:
PyNotebookExporter = str(error)
RNotebookExporter = str(error)
22 changes: 17 additions & 5 deletions nbsrc/cli.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""Command line conversion tool `nbsrc`
"""

import os
import argparse
from nbformat import writes as ipynb_writes
from nbformat.reader import NotJSONError
from nbrmd import readf, writef
from nbrmd import writes
from nbrmd.languages import get_default_language
from nbrmd.combine import combine_inputs_with_outputs
from nbformat.reader import NotJSONError
import argparse


def convert(nb_files, in_place=True, combine=True):
Expand All @@ -27,7 +30,7 @@ def convert(nb_files, in_place=True, combine=True):

nb = readf(nb_file)
main_language = get_default_language(nb)
ext_dest = '.R' if main_language is 'R' else '.py'
ext_dest = '.R' if main_language == 'R' else '.py'

if in_place:
if ext == '.ipynb':
Expand All @@ -42,8 +45,8 @@ def convert(nb_files, in_place=True, combine=True):
nb_outputs = readf(nb_dest)
combine_inputs_with_outputs(nb, nb_outputs)
msg = '(outputs were preserved)'
except (IOError, NotJSONError) as e:
msg = '(outputs could not be preserved: {})'.format(e)
except (IOError, NotJSONError) as error:
msg = '(outputs were not preserved: {})'.format(error)
print('R Markdown {} being converted to '
'Jupyter notebook {} {}'
.format(nb_file, nb_dest, msg))
Expand All @@ -56,6 +59,11 @@ def convert(nb_files, in_place=True, combine=True):


def cli(args=None):
"""
Command line parser
:param args:
:return:
"""
parser = argparse.ArgumentParser(description='Jupyter notebook '
'from/to R or Python script')
parser.add_argument('notebooks',
Expand All @@ -72,5 +80,9 @@ def cli(args=None):


def main():
"""
Entry point for the nbsrc script
:return:
"""
args = cli()
convert(args.notebooks, args.in_place, args.preserve_outputs)
12 changes: 6 additions & 6 deletions nbsrc/nbsrc.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Methods for the nbsrc package. Note that methods for notebooks are
found in the nbrmd package"""

import os


def readme():
"""
Contents of README.md
:return:
"""
"""Contents of README.md"""
readme_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'..', 'README.md')
with open(readme_path) as fh:
return fh.read()
with open(readme_path) as readme_file:
return readme_file.read()
6 changes: 5 additions & 1 deletion nbsrc/srcexporter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import nbrmd
"""
R and Py notebook exporters for nbconvert
"""

from traitlets import default
from nbconvert.exporters import Exporter
import nbrmd


class PyNotebookExporter(Exporter):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

setup(
name='nbsrc',
version='0.4.0',
version='0.4.3',
author='Marc Wouts',
author_email='[email protected]',
description='Jupyter notebooks from/to python and R scripts',
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import os
from shutil import copyfile
import pytest
import nbrmd
from nbsrc.cli import convert, cli
from .utils import list_all_notebooks, remove_outputs
Expand Down
8 changes: 4 additions & 4 deletions tests/test_nbconvert.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import nbsrc
import nbrmd
import os
import subprocess
import pytest
import nbrmd
import nbsrc
from .utils import list_all_notebooks
import subprocess
import os


@pytest.mark.skipif(isinstance(nbsrc.PyNotebookExporter, str),
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def list_all_notebooks(ext):
nb_path = os.path.dirname(os.path.abspath(__file__))
notebooks = []
for nb_file in os.listdir(nb_path):
file, nb_ext = os.path.splitext(nb_file)
_, nb_ext = os.path.splitext(nb_file)
if nb_ext.lower() == ext.lower():
notebooks.append(os.path.join(nb_path, nb_file))
return notebooks
Expand Down

0 comments on commit d32f7cf

Please sign in to comment.