Repository: mwouts/jupytext
Pull request #310 (2019-08-11 04:39:11)

Include tests in sdists
This lets downstream users and packagers check their installation.

codecov[bot]: # [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=h1) Report
> Merging [#310](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=desc) into [master](https://codecov.io/gh/mwouts/jupytext/commit/5422de54c767e19f68b0990db1303a8813ad8661?src=pr&el=desc) will **decrease** coverage by `10.18%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/mwouts/jupytext/pull/310/graphs/tree.svg?width=650&token=a1pNjsiILi&height=150&src=pr)](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           master     #310       +/-   ##
===========================================
- Coverage    99.2%   89.02%   -10.19%     
===========================================
  Files          70       70               
  Lines        6701     6533      -168     
===========================================
- Hits         6648     5816      -832     
- Misses         53      717      +664
```


| [Impacted Files](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_black.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ibGFjay5weQ==) | `29.47% <0%> (-70.53%)` | :arrow_down: |
| [tests/test\_mirror.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9taXJyb3IucHk=) | `41.89% <0%> (-58.11%)` | :arrow_down: |
| [tests/test\_read\_simple\_pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9wYW5kb2MucHk=) | `44.82% <0%> (-55.18%)` | :arrow_down: |
| [tests/test\_knitr\_spin.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9rbml0cl9zcGluLnB5) | `45.45% <0%> (-54.55%)` | :arrow_down: |
| [jupytext/pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-anVweXRleHQvcGFuZG9jLnB5) | `43.75% <0%> (-52.09%)` | :arrow_down: |
| [tests/test\_read\_simple\_rmd.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9ybWQucHk=) | `50% <0%> (-50%)` | :arrow_down: |
| [tests/test\_active\_cells.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9hY3RpdmVfY2VsbHMucHk=) | `52.54% <0%> (-47.46%)` | :arrow_down: |
| [tests/test\_contentsmanager.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jb250ZW50c21hbmFnZXIucHk=) | `66.86% <0%> (-33.14%)` | :arrow_down: |
| [tests/test\_rmd\_to\_ipynb.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ybWRfdG9faXB5bmIucHk=) | `75% <0%> (-25%)` | :arrow_down: |
| [tests/test\_cli.py](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jbGkucHk=) | `82.11% <0%> (-17.89%)` | :arrow_down: |
| ... and [28 more](https://codecov.io/gh/mwouts/jupytext/pull/310/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=footer). Last update [5422de5...04f254b](https://codecov.io/gh/mwouts/jupytext/pull/310?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index 99b9c2a6..f726639d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@ language: python
 python:
   - "3.6"
   - "pypy"
-  - "2.6"
   - "2.7"
   - "3.4"
   - "3.5"
@@ -16,12 +15,15 @@ matrix:
       sudo: required
 
 install:
-  # We use conda in order to install pandoc
-  # Actually, 2.6 here just means conda, with Python 3!
-  # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/use-conda-with-travis-ci.html
-  - if [[ "$TRAVIS_PYTHON_VERSION" == "2.6" ]]; then
+  # Python 2.7 and 3.4 are mapped to miniconda2 and miniconda3, respectively. This way we can test jupytext with pandoc
+  # Script adapted from https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/use-conda-with-travis-ci.html
+  - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]] || [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then
       sudo apt-get update;
-      wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
+      if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
+        wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh;
+      else
+        wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
+      fi;
       bash miniconda.sh -b -p $HOME/miniconda;
       export PATH="$HOME/miniconda/bin:$PATH";
       hash -r;
@@ -29,7 +31,7 @@ install:
       conda update -q conda;
       conda config --append channels conda-forge;
       conda info -a;
-      conda create -q -n jupytext-env python=3 --file requirements-dev.txt --file requirements.txt;
+      conda create -q -n jupytext-env --file requirements-dev.txt --file requirements.txt;
       source activate jupytext-env;
       conda install pandoc codecov -c conda-forge;
       pandoc -v;
diff --git a/HISTORY.rst b/HISTORY.rst
index ab1910c1..25a6329a 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,22 @@
 Release History
 ---------------
 
+1.2.2 (2019-09-??)
+++++++++++++++++++++++
+
+**Improvements**
+
+- Documented how to use Jupytext as a Python library (#317)
+- Mention of the server extension in the documentation (#304)
+- Test a `README.md` is as simple as `jupytext --execute README.md` (#303)
+- The default value of `as_version` in `jupytext.read` is `nbformat.NO_CONVERT`, as for `nbformat.read`
+
+**BugFixes**
+
+- Fix the usability of the `fmt` argument in `jupytext.read` (#312)
+- Fix the download notebook error when `c.notebook_extensions` has a custom value (#318)
+- Ignore string delimiters in commented text (#307)
+
 1.2.1 (2019-07-20)
 ++++++++++++++++++++++
 
diff --git a/MANIFEST.in b/MANIFEST.in
index a4a5f4aa..63e250bc 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,3 +2,7 @@ include LICENSE
 include *.md
 include *.txt
 include *.rst
+
+graft tests
+prune tests\__pycache__
+global-exclude *population.ipynb
diff --git a/README.md b/README.md
index 0531f996..a01757ed 100755
--- a/README.md
+++ b/README.md
@@ -55,9 +55,14 @@ If you want to use Jupytext within Jupyter Notebook or JupyterLab, make sure you
 
 ### Jupytext's contents manager
 
-Jupytext includes a contents manager for Jupyter that allows Jupyter to open and save notebooks as text files. When Jupytext's content manager is active in Jupyter, scripts and Markdown documents have a notebook icon.
+Jupytext provides a contents manager for Jupyter that allows Jupyter to open and save notebooks as text files. When Jupytext's content manager is active in Jupyter, scripts and Markdown documents have a notebook icon.
 
-If you don't have the notebook icon on text documents after a fresh restart of your Jupyter server, install the contents manager manually. Append
+In most cases, Jupytext's contents manager is activated automatically by Jupytext's server extension. When you restart either `jupyter lab` or `jupyter notebook`, you should see a line that looks like:
+```bash
+[I 10:28:31.646 LabApp] [Jupytext Server Extension] Changing NotebookApp.contents_manager_class from LargeFileManager to jupytext.TextFileContentsManager
+```
+
+If you don't have the notebook icon on text documents after a fresh restart of your Jupyter server, you can either enable our server extension explicitly (with `jupyter serverextension enable jupytext`), or install the contents manager manually. Append
 ```python
 c.NotebookApp.contents_manager_class = "jupytext.TextFileContentsManager"
 ```
diff --git a/docs/formats.md b/docs/formats.md
index 817fe570..457ece6a 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -65,10 +65,11 @@ to your `.jupyter/jupyter_notebook_config.py` file.
 
 The `percent` format is a representation of Jupyter notebooks as scripts, in which cells are delimited with a commented double percent sign `# %%`. The format was introduced by Spyder five years ago, and is now supported by many editors, including
 - [Spyder IDE](https://docs.spyder-ide.org/editor.html#defining-code-cells),
-- [Hydrogen](https://atom.io/packages/hydrogen), a package for Atom,
-- [VS Code](https://code.visualstudio.com/) with the [vscodeJupyter](https://marketplace.visualstudio.com/items?itemName=donjayamanne.jupyter) extension,
-- [Python Tools for Visual Studio](https://github.com/Microsoft/PTVS),
-- and [PyCharm Professional](https://www.jetbrains.com/pycharm/).
+- [Hydrogen](https://nteract.gitbooks.io/hydrogen/docs/Usage/NotebookFiles.html#notebook-export), a package for Atom,
+- [VS
+  Code](https://code.visualstudio.com/docs/python/jupyter-support#_jupyter-code-cells),
+- [Python Tools for Visual Studio](https://docs.microsoft.com/en-us/visualstudio/python/python-interactive-repl-in-visual-studio?view=vs-2019#work-with-code-cells),
+- and [PyCharm Professional](https://www.jetbrains.com/help/pycharm/editing-jupyter-notebook-files.html#edit-content).
 
 Our implementation of the `percent` format is compatible with the original specifications by Spyder. We extended the format to allow markdown cells and cell metadata. Cell headers have the following structure:
 ```python
diff --git a/docs/index.md b/docs/index.md
index 904a263a..2adc00fb 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,5 +7,6 @@
 * [Installation](install)
 * [Using Jupytext in Jupyter](using-server)
 * [Using at the command line](using-cli)
+* [Using as a Python libary](using-library)
 * [Supported document formats](formats)
 * [Frequently Asked Questions](faq)
diff --git a/docs/install.md b/docs/install.md
index 7fc8b5fb..8b101478 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -20,13 +20,18 @@ If you want to use Jupytext within Jupyter Notebook or JupyterLab, make sure you
 
 ## Jupytext's contents manager
 
-Jupytext includes a contents manager for Jupyter that allows Jupyter to open and save notebooks as text files. When Jupytext's content manager is active in Jupyter, scripts and Markdown documents have a notebook icon.
+Jupytext provides a contents manager for Jupyter that allows Jupyter to open and save notebooks as text files. When Jupytext's content manager is active in Jupyter, scripts and Markdown documents have a notebook icon.
 
-If you don't have the notebook icon on text documents after a fresh restart of your Jupyter server, install the contents manager manually. Append
+In most cases, Jupytext's contents manager is activated automatically by Jupytext's server extension. When you restart either `jupyter lab` or `jupyter notebook`, you should see a line that looks like:
+```bash
+[I 10:28:31.646 LabApp] [Jupytext Server Extension] Changing NotebookApp.contents_manager_class from LargeFileManager to jupytext.TextFileContentsManager
+```
+
+If you don't have the notebook icon on text documents after a fresh restart of your Jupyter server, you can either enable our server extension explicitly (with `jupyter serverextension enable jupytext`), or install the contents manager manually. Append
 ```python
 c.NotebookApp.contents_manager_class = "jupytext.TextFileContentsManager"
 ```
-to your `.jupyter/jupyter_notebook_config.py` file (generate a Jupyter config, if you don't have one yet, with `jupyter notebook --generate-config`). Our contents manager accepts a few options: default formats, default metadata filter, etc &mdash; read more on this [below](using-server.html#global-configuration). Then, restart Jupyter Notebook or JupyterLab, either from the JupyterHub interface or from the command line with
+to your `.jupyter/jupyter_notebook_config.py` file (generate a Jupyter config, if you don't have one yet, with `jupyter notebook --generate-config`). Our contents manager accepts a few options: default formats, default metadata filter, etc. Then, restart Jupyter Notebook or JupyterLab, either from the JupyterHub interface or from the command line with
 ```bash
 jupyter notebook # or lab
 ```
diff --git a/docs/using-cli.md b/docs/using-cli.md
index 060ffb82..8098e076 100644
--- a/docs/using-cli.md
+++ b/docs/using-cli.md
@@ -125,24 +125,3 @@ Please note that
 metadata to be added back to the script. Remove the filter if you want to store Jupytext's settings, or the kernel information, in the text file.
 - Cell metadata are available in the `light` and `percent` formats, as well as in the Markdown and R Markdown formats. R scripts in `spin` format support cell metadata for code cells only. Sphinx Gallery scripts in `sphinx` format do not support cell metadata.
 - By default, a few cell metadata are not included in the text representation of the notebook. And only the most standard notebook metadata are exported. Learn more on this in the sections for [notebook specific](using-server.html#per-notebook-configuration) and [global settings](using-server.html#metadata-filtering) for metadata filtering.
-
-## Reading notebooks in Python
-
-Jupytext provides the same `read`, `write`, `reads` and `writes` functions as `nbformat`. You can use `jupytext`'s functions as drop-in replacements for `nbformat`'s ones. Jupytext's implementation provides an additional `fmt` argument, which can be any of `py`, `md`, `jl:percent`, etc. If not explicitly provided, the argument is inferred from the file extension.
-
-```python
-import jupytext
-
-# Read a notebook from a file 
-jupytext.read('notebook.md')
-
-# Read a notebook from a string
-jupytext.reads(text, fmt='md')
-
-# Return the text representation of a notebook
-jupytext.writes(notebook, fmt='py:percent')
-
-# Write a notebook to a file in the desired format
-jupytext.write(notebook, 'notebook.py')
-jupytext.write(notebook, 'notebook.py', fmt='py:percent')
-```
diff --git a/docs/using-library.md b/docs/using-library.md
new file mode 100644
index 00000000..db09587f
--- /dev/null
+++ b/docs/using-library.md
@@ -0,0 +1,45 @@
+# Using the Jupytext Python library
+
+Jupytext provides the same `read`, `write`, `reads` and `writes` functions as `nbformat`.
+You can use `jupytext`'s functions as drop-in replacements for `nbformat`'s ones.
+
+## Reading notebooks from many text formats
+
+To read text files as notebooks, simply provide the path to a Jupytext-supported
+format.
+```python
+import jupytext
+
+# Read a notebook from a file 
+ntbk = jupytext.read('notebook.md')
+
+# Read a notebook from a string
+jupytext.reads(text, fmt='md')
+```
+
+Jupytext will read in the content and infer metadata about the file
+from the YAML header (if there is one). If there is no Jupytext header,
+then Jupytext will make some assumptions about the format based on the
+file extension.
+
+This function returns an instance of an `nbformat` `NotebookNode`. You
+can find more information for working with this notebook representation
+in the [nbformat documentation](https://nbformat.readthedocs.io).
+
+## Writing notebooks to many text files
+
+You can also write in-memory notebooks to a variety of text formats by
+using `jupytext.write`.
+
+Jupytext's implementation provides an additional `fmt` argument,
+which can be any of the accepted Jupytext extensions (e.g., `py`, `md`, `jl:percent`)
+If not explicitly provided, the argument is inferred from the file extension.
+
+```python
+# Return the text representation of a notebook
+jupytext.writes(notebook, fmt='py:percent')
+
+# Write a notebook to a file in the desired format
+jupytext.write(notebook, 'notebook.py')
+jupytext.write(notebook, 'notebook.py', fmt='py:percent')
+```
diff --git a/jupytext/cli.py b/jupytext/cli.py
index 72df6964..f6a93069 100644
--- a/jupytext/cli.py
+++ b/jupytext/cli.py
@@ -213,7 +213,8 @@ def jupytext(args=None):
     if not args.to and not args.output and not args.sync \
             and not args.pipe and not args.check \
             and not args.test and not args.test_strict \
-            and not args.update_metadata and not args.set_kernel:
+            and not args.update_metadata and not args.set_kernel \
+            and not args.execute:
         raise ValueError('Please select an action')
 
     if args.output and len(args.notebooks) != 1:
diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py
index 8d2a43f1..9fb64b57 100644
--- a/jupytext/contentsmanager.py
+++ b/jupytext/contentsmanager.py
@@ -357,7 +357,7 @@ def build_jupytext_contents_manager_class(base_contents_manager_class):
                         path_inputs = alt_path
                         fmt_inputs = alt_fmt
                         model_outputs = model
-                        model = self.get(alt_path, content=content, type=type, format=format,
+                        model = self.get(alt_path, content=content, type='notebook', format=format,
                                          load_alternative_format=False)
                         break
             # Outputs taken from ipynb if in group, if file exists
@@ -366,7 +366,7 @@ def build_jupytext_contents_manager_class(base_contents_manager_class):
                     if alt_path.endswith('.ipynb') and self.exists(alt_path):
                         self.log.info(u'Reading OUTPUTS from {}'.format(alt_path))
                         path_outputs = alt_path
-                        model_outputs = self.get(alt_path, content=content, type=type, format=format,
+                        model_outputs = self.get(alt_path, content=content, type='notebook', format=format,
                                                  load_alternative_format=False)
                         break
 
diff --git a/jupytext/jupytext.py b/jupytext/jupytext.py
index 34aa1373..1c8135a0 100644
--- a/jupytext/jupytext.py
+++ b/jupytext/jupytext.py
@@ -188,7 +188,7 @@ class TextNotebookConverter(NotebookReader, NotebookWriter):
         return '\n'.join(header + lines)
 
 
-def reads(text, fmt, as_version=4, **kwargs):
+def reads(text, fmt, as_version=nbformat.NO_CONVERT, **kwargs):
     """
     Read a notebook from a string
 
@@ -227,7 +227,7 @@ def reads(text, fmt, as_version=4, **kwargs):
     return notebook
 
 
-def read(fp, as_version=4, fmt=None, **kwargs):
+def read(fp, as_version=nbformat.NO_CONVERT, fmt=None, **kwargs):
     """"
     Read a notebook from a file name or a file object
 
@@ -237,6 +237,9 @@ def read(fp, as_version=4, fmt=None, **kwargs):
     :param kwargs: (not used) additional parameters for nbformat.read
     :return: the notebook
     """
+    if as_version != nbformat.NO_CONVERT and type(as_version) != int:
+        raise TypeError("Second argument 'as_version' should be either nbformat.NO_CONVERT, or an integer.")
+
     if fp == '-':
         text = sys.stdin.read()
         return reads(text, fmt)
@@ -246,6 +249,8 @@ def read(fp, as_version=4, fmt=None, **kwargs):
         fp = str(fp)
         _, ext = os.path.splitext(fp)
         fmt = copy(fmt or {})
+        if not isinstance(fmt, dict):
+            fmt = long_form_one_format(fmt)
         fmt.update({'extension': ext})
         with io.open(fp, encoding='utf-8') as stream:
             return read(stream, as_version=as_version, fmt=fmt, **kwargs)
diff --git a/jupytext/languages.py b/jupytext/languages.py
index 4580cb63..7668e898 100644
--- a/jupytext/languages.py
+++ b/jupytext/languages.py
@@ -26,6 +26,8 @@ _SCRIPT_EXTENSIONS = {'.py': {'language': 'python', 'comment': '#'},
 _COMMENT_CHARS = [_SCRIPT_EXTENSIONS[ext]['comment'] for ext in _SCRIPT_EXTENSIONS if
                   _SCRIPT_EXTENSIONS[ext]['comment'] != '#']
 
+_COMMENT = {_SCRIPT_EXTENSIONS[ext]['language']: _SCRIPT_EXTENSIONS[ext]['comment'] for ext in _SCRIPT_EXTENSIONS}
+
 _JUPYTER_LANGUAGES = _JUPYTER_LANGUAGES + [
     _SCRIPT_EXTENSIONS[ext]['language'] for ext in _SCRIPT_EXTENSIONS if
     _SCRIPT_EXTENSIONS[ext]['language'] not in _JUPYTER_LANGUAGES]
diff --git a/jupytext/magics.py b/jupytext/magics.py
index dfd7af2d..361ebaaf 100644
--- a/jupytext/magics.py
+++ b/jupytext/magics.py
@@ -2,7 +2,7 @@
 
 import re
 from .stringparser import StringParser
-from .languages import _SCRIPT_EXTENSIONS
+from .languages import _SCRIPT_EXTENSIONS, _COMMENT
 
 # A magic expression is a line or cell or metakernel magic (#94, #61) escaped zero, or multiple times
 _MAGIC_RE = {_SCRIPT_EXTENSIONS[ext]['language']: re.compile(
@@ -14,7 +14,6 @@ _MAGIC_NOT_ESC_RE = {_SCRIPT_EXTENSIONS[ext]['language']: re.compile(
     r"^({0} |{0})*(%|%%|%%%)[a-zA-Z](.*){0}\s*noescape".format(
         _SCRIPT_EXTENSIONS[ext]['comment'])) for ext in _SCRIPT_EXTENSIONS}
 _LINE_CONTINUATION_RE = re.compile(r'.*\\\s*$')
-_COMMENT = {_SCRIPT_EXTENSIONS[ext]['language']: _SCRIPT_EXTENSIONS[ext]['comment'] for ext in _SCRIPT_EXTENSIONS}
 
 # Commands starting with a question or exclamation mark have to be escaped
 _PYTHON_HELP_OR_BASH_CMD = re.compile(r"^(# |#)*(\?|!)\s*[A-Za-z]")
diff --git a/jupytext/stringparser.py b/jupytext/stringparser.py
index b1a89d3b..17b28fa2 100644
--- a/jupytext/stringparser.py
+++ b/jupytext/stringparser.py
@@ -1,5 +1,6 @@
 """A simple file parser that can tell whether the first character of a line
 is quoted or not"""
+from .languages import _COMMENT
 
 
 class StringParser:
@@ -11,6 +12,7 @@ class StringParser:
     def __init__(self, language):
         self.ignore = language is None
         self.python = language != 'R'
+        self.comment = _COMMENT.get(language)
 
     def is_quoted(self):
         """Is the next line quoted?"""
@@ -23,6 +25,10 @@ class StringParser:
         if self.ignore:
             return
 
+        # Do not search for quotes when the line is commented out (and not quoted)
+        if not self.is_quoted() and self.comment is not None and line.startswith(self.comment):
+            return
+
         for i, char in enumerate(line):
             if char not in ['"', "'"]:
                 continue
diff --git a/jupytext/version.py b/jupytext/version.py
index 283f8099..531663e8 100644
--- a/jupytext/version.py
+++ b/jupytext/version.py
@@ -1,3 +1,3 @@
 """Jupytext's version number"""
 
-__version__ = '1.2.1'
+__version__ = '1.2.2-dev'


Test patch:
diff --git a/tests/test_contentsmanager.py b/tests/test_contentsmanager.py
index 286c1d47..bdc8f37f 100644
--- a/tests/test_contentsmanager.py
+++ b/tests/test_contentsmanager.py
@@ -1473,6 +1473,23 @@ def test_notebook_extensions(tmpdir):
     assert model['type'] == 'file'
 
 
+def test_download_file_318(tmpdir):
+    tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
+    tmp_py = str(tmpdir.join('notebook.py'))
+
+    nb = new_notebook()
+    nb.metadata['jupytext'] = {'formats': 'ipynb,py'}
+    write(nb, tmp_ipynb)
+    write(nb, tmp_py)
+
+    cm = jupytext.TextFileContentsManager()
+    cm.root_dir = str(tmpdir)
+    cm.notebook_extensions = 'ipynb'
+
+    model = cm.get('notebook.ipynb', content=True, type=None, format=None)
+    assert model['type'] == 'notebook'
+
+
 def test_markdown_and_r_extensions(tmpdir):
     tmp_r = str(tmpdir.join('script.r'))
     tmp_markdown = str(tmpdir.join('notebook.markdown'))
diff --git a/tests/test_execute.py b/tests/test_execute.py
index 85aff88e..3c972af4 100644
--- a/tests/test_execute.py
+++ b/tests/test_execute.py
@@ -1,4 +1,5 @@
 from .utils import requires_nbconvert, requires_ir_kernel
+import pytest
 from jupytext import read
 from jupytext.cli import jupytext
 
@@ -53,6 +54,40 @@ def test_execute(tmpdir):
     assert nb.cells[0].outputs[0]['data'] == {'text/plain': '3'}
 
 
+@requires_nbconvert
+def test_execute_readme_ok(tmpdir):
+    tmp_md = str(tmpdir.join('notebook.md'))
+
+    with open(tmp_md, 'w') as fp:
+        fp.write("""
+A readme with correct instructions
+
+```python
+1 + 2
+```
+""")
+
+    jupytext(args=[tmp_md, '--execute'])
+
+
+@requires_nbconvert
+def test_execute_readme_not_ok(tmpdir):
+    tmp_md = str(tmpdir.join('notebook.md'))
+
+    with open(tmp_md, 'w') as fp:
+        fp.write("""
+A readme with incorrect instructions (a is not defined)
+
+```python
+a + 1
+```
+""")
+
+    import nbconvert
+    with pytest.raises(nbconvert.preprocessors.execute.CellExecutionError, match="is not defined"):
+        jupytext(args=[tmp_md, '--execute'])
+
+
 @requires_nbconvert
 def test_execute_sync(tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
diff --git a/tests/test_jupytext_read.py b/tests/test_jupytext_read.py
new file mode 100644
index 00000000..8c4e09a0
--- /dev/null
+++ b/tests/test_jupytext_read.py
@@ -0,0 +1,22 @@
+import pytest
+import jupytext
+from jupytext.compare import compare_notebooks
+
+
+def test_as_version_has_appropriate_type():
+    with pytest.raises(TypeError):
+        jupytext.read('script.py', 'py:percent')
+
+
+def test_read_file_with_explicit_fmt(tmpdir):
+    tmp_py = str(tmpdir.join('notebook.py'))
+
+    with open(tmp_py, 'w') as fp:
+        fp.write("""# %%
+1 + 1
+""")
+
+    nb1 = jupytext.read(tmp_py)
+    nb2 = jupytext.read(tmp_py, fmt='py:percent')
+
+    compare_notebooks(nb1, nb2)
diff --git a/tests/test_read_simple_python.py b/tests/test_read_simple_python.py
index 70519979..feb46930 100644
--- a/tests/test_read_simple_python.py
+++ b/tests/test_read_simple_python.py
@@ -4,6 +4,7 @@ from nbformat.v4.nbbase import new_markdown_cell, new_code_cell, new_notebook
 from jupytext.compare import compare
 import jupytext
 from jupytext.compare import compare_notebooks
+from .utils import skip_if_dict_is_not_ordered
 
 
 def test_read_simple_file(pynb="""# ---
@@ -667,6 +668,49 @@ def test_notebook_one_blank_line_before_first_markdown_cell(script="""
             assert lines[-1]
 
 
+def test_read_markdown_cell_with_triple_quote_307(
+        script="""# This script test that commented triple quotes '''
+# do not impede the correct identification of Markdown cells
+
+# Here is Markdown cell number 2 '''
+"""):
+    notebook = jupytext.reads(script, 'py')
+    assert len(notebook.cells) == 2
+    assert notebook.cells[0].cell_type == 'markdown'
+    assert notebook.cells[0].source == """This script test that commented triple quotes '''
+do not impede the correct identification of Markdown cells"""
+    assert notebook.cells[1].cell_type == 'markdown'
+    assert notebook.cells[1].source == "Here is Markdown cell number 2 '''"
+
+    script2 = jupytext.writes(notebook, 'py')
+    compare(script2, script)
+
+
+@skip_if_dict_is_not_ordered
+def test_read_explicit_markdown_cell_with_triple_quote_307(
+        script="""# {{{ {"special": "metadata", "cell_type": "markdown"}
+# some text '''
+# }}}
+
+print('hello world')
+
+# {{{ {"special": "metadata", "cell_type": "markdown"}
+# more text '''
+# }}}
+"""):
+    notebook = jupytext.reads(script, 'py')
+    assert len(notebook.cells) == 3
+    assert notebook.cells[0].cell_type == 'markdown'
+    assert notebook.cells[0].source == "some text '''"
+    assert notebook.cells[1].cell_type == 'code'
+    assert notebook.cells[1].source == "print('hello world')"
+    assert notebook.cells[2].cell_type == 'markdown'
+    assert notebook.cells[2].source == "more text '''"
+
+    script2 = jupytext.writes(notebook, 'py')
+    compare(script2, script)
+
+
 def test_round_trip_markdown_cell_with_magic():
     notebook = new_notebook(cells=[new_markdown_cell('IPython has magic commands like\n%quickref')],
                             metadata={'jupytext': {'main_language': 'python'}})