Repository: mwouts/jupytext
Pull request #268 (2019-06-29 20:51:11)

Jupytext read/write functions as drop in replacement for nbformat's ones
- Previous readf/writef functions marked as deprecated
- They are replaced with read/write which can take a file name, like `nbformat`'s functions. Argument `fmt` goes after the `nbformat` version for improved compatibility with `read` and `write` of the `nbformat` package.

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

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

```diff
@@            Coverage Diff             @@
##           master     #268      +/-   ##
==========================================
- Coverage   99.07%   99.02%   -0.06%     
==========================================
  Files          66       66              
  Lines        6519     6565      +46     
==========================================
+ Hits         6459     6501      +42     
- Misses         60       64       +4
```


| [Impacted Files](https://codecov.io/gh/mwouts/jupytext/pull/268?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_cli.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jbGkucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_read\_simple\_ipynb.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9pcHluYi5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_black.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ibGFjay5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [jupytext/\_\_init\_\_.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-anVweXRleHQvX19pbml0X18ucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_unicode.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF91bmljb2RlLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_jupytext\_errors.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9qdXB5dGV4dF9lcnJvcnMucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_load\_multiple.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9sb2FkX211bHRpcGxlLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [jupytext/version.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-anVweXRleHQvdmVyc2lvbi5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_pep8.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9wZXA4LnB5) | `98.38% <100%> (ø)` | :arrow_up: |
| [tests/test\_save\_multiple.py](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9zYXZlX211bHRpcGxlLnB5) | `100% <100%> (ø)` | :arrow_up: |
| ... and [7 more](https://codecov.io/gh/mwouts/jupytext/pull/268/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwouts/jupytext/pull/268?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/268?src=pr&el=footer). Last update [ad7e48f...b893fbe](https://codecov.io/gh/mwouts/jupytext/pull/268?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 962dbb0f..1a5f3409 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,14 @@
 Release History
 ---------------
 
+1.2.0 (2019-06-??)
+++++++++++++++++++++++
+
+**Improvements**
+
+- ``jupytext``'s ``read`` and ``write`` functions can be used as drop-in remplacements for ``nbformat``'s ones (#262).
+
+
 1.1.7 (2019-06-23)
 ++++++++++++++++++++++
 
diff --git a/docs/using-cli.md b/docs/using-cli.md
index d5a34f4d..e0b2ba38 100644
--- a/docs/using-cli.md
+++ b/docs/using-cli.md
@@ -76,18 +76,21 @@ metadata to be added back to the script. Remove the filter if you want to store
 
 ## Reading notebooks in Python
 
-You can also manipulate notebooks in a Python shell or script using Jupytext's main functions:
+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
-# Read a notebook from a file. Format can be any of 'py', 'md', 'jl:percent', ...
-readf(nb_file, fmt=None)
+import jupytext
 
-# Read a notebook from a string. Here, format should contain at least the file extension.
-reads(text, fmt)
+# Read a notebook from a file 
+jupytext.read('notebook.md')
 
-# Return the text representation for a notebook in the desired format.
-writes(notebook, fmt)
+# Read a notebook from a string
+jupytext.reads(text, fmt='md')
 
-# Write a notebook to a file in the desired format.
-writef(notebook, nb_file, fmt=None)
+# 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/__init__.py b/jupytext/__init__.py
index 6e6645e7..166b1694 100644
--- a/jupytext/__init__.py
+++ b/jupytext/__init__.py
@@ -1,6 +1,6 @@
 """Read and write Jupyter notebooks as text files"""
 
-from .jupytext import readf, writef, writes, reads
+from .jupytext import read, write, readf, writef, writes, reads
 from .formats import NOTEBOOK_EXTENSIONS, guess_format, get_format_implementation
 from .version import __version__
 
@@ -70,6 +70,6 @@ def _jupyter_nbextension_paths():  # pragma: no cover
         require="jupytext/index")]
 
 
-__all__ = ['readf', 'writef', 'writes', 'reads',
+__all__ = ['read', 'write', 'readf', 'writef', 'writes', 'reads',
            'NOTEBOOK_EXTENSIONS', 'guess_format', 'get_format_implementation',
            'TextFileContentsManager', '__version__']
diff --git a/jupytext/cli.py b/jupytext/cli.py
index 03ec2693..755ac9cf 100644
--- a/jupytext/cli.py
+++ b/jupytext/cli.py
@@ -8,7 +8,7 @@ import subprocess
 import argparse
 import json
 from copy import copy
-from .jupytext import readf, reads, writef, writes
+from .jupytext import read, reads, write, writes
 from .formats import _VALID_FORMAT_OPTIONS, _BINARY_FORMAT_OPTIONS, check_file_version
 from .formats import long_form_one_format, long_form_multiple_formats, short_form_one_format, auto_ext_from_metadata
 from .header import recursive_update
@@ -105,6 +105,10 @@ def parse_jupytext_args(args=None):
                              'Input cells are taken from the file that was last modified, and outputs are read '
                              'from the ipynb file, if present.',
                         action='store_true')
+    action.add_argument('--warn-only', '-w',
+                        action='store_true',
+                        help='Only issue a warning and continue processing other notebooks when the conversion of a '
+                             'given notebook fails')
     action.add_argument('--test',
                         action='store_true',
                         help='Test that notebook is stable under a round trip conversion, up to the expected changes')
@@ -173,7 +177,7 @@ def jupytext(args=None):
             log(nb_file)
 
     def writef_git_add(notebook_, nb_file_, fmt_):
-        writef(notebook_, nb_file_, fmt_)
+        write(notebook_, nb_file_, fmt=fmt_)
         if args.pre_commit:
             system('git', 'add', nb_file_)
 
@@ -221,158 +225,164 @@ def jupytext(args=None):
             notebooks.append(pattern)
 
     for nb_file in notebooks:
-        if nb_file == '-' and args.sync:
-            raise ValueError('Cannot sync a notebook on stdin')
-
-        nb_dest = args.output or (None if not args.to
-                                  else ('-' if nb_file == '-' else
-                                        full_path(base_path(nb_file, args.input_format), args.to)))
-
-        # Just acting on metadata / pipe => save in place
-        if not nb_dest and not args.sync:
-            nb_dest = nb_file
-
-        if nb_dest == '-':
-            args.quiet = True
-
-        # I. ### Read the notebook ###
-        fmt = copy(args.input_format) or {}
-        set_format_options(fmt, args.format_options)
-        log('[jupytext] Reading {}{}'.format(
-            nb_file if nb_file != '-' else 'stdin',
-            ' in format {}'.format(short_form_one_format(fmt)) if 'extension' in fmt else ''))
-
-        notebook = readf(nb_file, fmt)
-        if not fmt:
-            text_representation = notebook.metadata.get('jupytext', {}).get('text_representation', {})
-            ext = os.path.splitext(nb_file)[1]
-            if text_representation.get('extension') == ext:
-                fmt = {key: text_representation[key] for key in text_representation if
-                       key in ['extension', 'format_name']}
-            elif ext:
-                fmt = {'extension': ext}
-
-        # Compute actual extension when using script/auto, and update nb_dest if necessary
-        dest_fmt = args.to
-        if dest_fmt and dest_fmt['extension'] == '.auto':
-            auto_ext = auto_ext_from_metadata(notebook.metadata)
-            if not auto_ext:
-                raise ValueError('The notebook has no language information. '
-                                 'Please provide an explicit script extension.')
-            dest_fmt['extension'] = auto_ext
-            if not args.output and nb_file != '-':
-                nb_dest = full_path(base_path(nb_file, args.input_format), dest_fmt)
-
-            # Set the kernel
-        if args.set_kernel:
-            if args.set_kernel == '-':
-                language = notebook.metadata.get('jupytext', {}).get('main_language') \
-                           or notebook.metadata['kernelspec']['language']
-                if not language:
-                    raise ValueError('Cannot infer a kernel as notebook language is not defined')
-
-                kernelspec = kernelspec_from_language(language)
-                if not kernelspec:
-                    raise ValueError('Found no kernel for {}'.format(language))
-            else:
+        try:
+            if nb_file == '-' and args.sync:
+                raise ValueError('Cannot sync a notebook on stdin')
+
+            nb_dest = args.output or (None if not args.to
+                                      else ('-' if nb_file == '-' else
+                                            full_path(base_path(nb_file, args.input_format), args.to)))
+
+            # Just acting on metadata / pipe => save in place
+            if not nb_dest and not args.sync:
+                nb_dest = nb_file
+
+            if nb_dest == '-':
+                args.quiet = True
+
+            # I. ### Read the notebook ###
+            fmt = copy(args.input_format) or {}
+            set_format_options(fmt, args.format_options)
+            log('[jupytext] Reading {}{}'.format(
+                nb_file if nb_file != '-' else 'stdin',
+                ' in format {}'.format(short_form_one_format(fmt)) if 'extension' in fmt else ''))
+
+            notebook = read(nb_file, fmt=fmt)
+            if not fmt:
+                text_representation = notebook.metadata.get('jupytext', {}).get('text_representation', {})
+                ext = os.path.splitext(nb_file)[1]
+                if text_representation.get('extension') == ext:
+                    fmt = {key: text_representation[key] for key in text_representation if
+                           key in ['extension', 'format_name']}
+                elif ext:
+                    fmt = {'extension': ext}
+
+            # Compute actual extension when using script/auto, and update nb_dest if necessary
+            dest_fmt = args.to
+            if dest_fmt and dest_fmt['extension'] == '.auto':
+                auto_ext = auto_ext_from_metadata(notebook.metadata)
+                if not auto_ext:
+                    raise ValueError('The notebook has no language information. '
+                                     'Please provide an explicit script extension.')
+                dest_fmt['extension'] = auto_ext
+                if not args.output and nb_file != '-':
+                    nb_dest = full_path(base_path(nb_file, args.input_format), dest_fmt)
+
+                # Set the kernel
+            if args.set_kernel:
+                if args.set_kernel == '-':
+                    language = notebook.metadata.get('jupytext', {}).get('main_language') \
+                               or notebook.metadata['kernelspec']['language']
+                    if not language:
+                        raise ValueError('Cannot infer a kernel as notebook language is not defined')
+
+                    kernelspec = kernelspec_from_language(language)
+                    if not kernelspec:
+                        raise ValueError('Found no kernel for {}'.format(language))
+                else:
+                    try:
+                        kernelspec = get_kernel_spec(args.set_kernel)
+                    except KeyError:
+                        raise KeyError('Please choose a kernel name among {}'
+                                       .format([name for name in find_kernel_specs()]))
+                    kernelspec = {'name': args.set_kernel,
+                                  'language': kernelspec.language,
+                                  'display_name': kernelspec.display_name}
+
+                args.update_metadata['kernelspec'] = kernelspec
+
+            # Update the metadata
+            if args.update_metadata:
+                log("[jupytext] Updating notebook metadata with '{}'".format(json.dumps(args.update_metadata)))
+                # Are we updating a text file that has a metadata filter? #212
+                if fmt['extension'] != '.ipynb' and \
+                        notebook.metadata.get('jupytext', {}).get('notebook_metadata_filter') == '-all':
+                    notebook.metadata.get('jupytext', {}).pop('notebook_metadata_filter')
+                recursive_update(notebook.metadata, args.update_metadata)
+
+                if 'kernelspec' in args.update_metadata and 'main_language' in notebook.metadata.get('jupytext', {}):
+                    notebook.metadata['jupytext'].pop('main_language')
+
+            # Read paired notebooks
+            if args.sync:
+                set_prefix_and_suffix(fmt, notebook, nb_file)
+                notebook, inputs_nb_file, outputs_nb_file = load_paired_notebook(notebook, fmt, nb_file, log)
+
+            # II. ### Apply commands onto the notebook ###
+            # Pipe the notebook into the desired commands
+            for cmd in args.pipe or []:
+                notebook = pipe_notebook(notebook, cmd, args.pipe_fmt)
+
+            # and/or test the desired commands onto the notebook
+            for cmd in args.check or []:
+                pipe_notebook(notebook, cmd, args.pipe_fmt, update=False)
+
+            # III. ### Possible actions ###
+            modified = args.update_metadata or args.pipe or args.set_kernel
+            # a. Test round trip conversion
+            if args.test or args.test_strict:
                 try:
-                    kernelspec = get_kernel_spec(args.set_kernel)
-                except KeyError:
-                    raise KeyError('Please choose a kernel name among {}'
-                                   .format([name for name in find_kernel_specs()]))
-                kernelspec = {'name': args.set_kernel,
-                              'language': kernelspec.language,
-                              'display_name': kernelspec.display_name}
-
-            args.update_metadata['kernelspec'] = kernelspec
-
-        # Update the metadata
-        if args.update_metadata:
-            log("[jupytext] Updating notebook metadata with '{}'".format(json.dumps(args.update_metadata)))
-            # Are we updating a text file that has a metadata filter? #212
-            if fmt['extension'] != '.ipynb' and \
-                    notebook.metadata.get('jupytext', {}).get('notebook_metadata_filter') == '-all':
-                notebook.metadata.get('jupytext', {}).pop('notebook_metadata_filter')
-            recursive_update(notebook.metadata, args.update_metadata)
-
-            if 'kernelspec' in args.update_metadata and 'main_language' in notebook.metadata.get('jupytext', {}):
-                notebook.metadata['jupytext'].pop('main_language')
-
-        # Read paired notebooks
-        if args.sync:
-            set_prefix_and_suffix(fmt, notebook, nb_file)
-            notebook, inputs_nb_file, outputs_nb_file = load_paired_notebook(notebook, fmt, nb_file, log)
-
-        # II. ### Apply commands onto the notebook ###
-        # Pipe the notebook into the desired commands
-        for cmd in args.pipe or []:
-            notebook = pipe_notebook(notebook, cmd, args.pipe_fmt)
-
-        # and/or test the desired commands onto the notebook
-        for cmd in args.check or []:
-            pipe_notebook(notebook, cmd, args.pipe_fmt, update=False)
-
-        # III. ### Possible actions ###
-        modified = args.update_metadata or args.pipe or args.set_kernel
-        # a. Test round trip conversion
-        if args.test or args.test_strict:
-            try:
-                test_round_trip_conversion(notebook, dest_fmt,
-                                           update=args.update,
-                                           allow_expected_differences=not args.test_strict,
-                                           stop_on_first_error=args.stop_on_first_error)
-            except NotebookDifference as err:
-                round_trip_conversion_errors += 1
-                sys.stdout.write('{}: {}'.format(nb_file, str(err)))
-            continue
+                    test_round_trip_conversion(notebook, dest_fmt,
+                                               update=args.update,
+                                               allow_expected_differences=not args.test_strict,
+                                               stop_on_first_error=args.stop_on_first_error)
+                except NotebookDifference as err:
+                    round_trip_conversion_errors += 1
+                    sys.stdout.write('{}: {}'.format(nb_file, str(err)))
+                continue
 
-        # b. Output to the desired file or format
-        if nb_dest:
-            if nb_dest == nb_file and not dest_fmt:
-                dest_fmt = fmt
-
-            # Test consistency between dest name and output format
-            if dest_fmt and nb_dest != '-':
-                base_path(nb_dest, dest_fmt)
-
-            # Describe what jupytext is doing
-            if os.path.isfile(nb_dest) and args.update:
-                if not nb_dest.endswith('.ipynb'):
-                    raise ValueError('--update is only for ipynb files')
-                action = ' (destination file updated)'
-                check_file_version(notebook, nb_file, nb_dest)
-                combine_inputs_with_outputs(notebook, readf(nb_dest), fmt)
-            elif os.path.isfile(nb_dest):
-                action = ' (destination file replaced)'
+            # b. Output to the desired file or format
+            if nb_dest:
+                if nb_dest == nb_file and not dest_fmt:
+                    dest_fmt = fmt
+
+                # Test consistency between dest name and output format
+                if dest_fmt and nb_dest != '-':
+                    base_path(nb_dest, dest_fmt)
+
+                # Describe what jupytext is doing
+                if os.path.isfile(nb_dest) and args.update:
+                    if not nb_dest.endswith('.ipynb'):
+                        raise ValueError('--update is only for ipynb files')
+                    action = ' (destination file updated)'
+                    check_file_version(notebook, nb_file, nb_dest)
+                    combine_inputs_with_outputs(notebook, read(nb_dest), fmt=fmt)
+                elif os.path.isfile(nb_dest):
+                    action = ' (destination file replaced)'
+                else:
+                    action = ''
+
+                log('[jupytext] Writing {nb_dest}{format}{action}'
+                    .format(nb_dest=nb_dest,
+                            format=' in format ' + short_form_one_format(
+                                dest_fmt) if dest_fmt and 'format_name' in dest_fmt else '',
+                            action=action))
+                writef_git_add(notebook, nb_dest, dest_fmt)
+
+            # c. Synchronize paired notebooks
+            if args.sync:
+                # Also update the original notebook if the notebook was modified
+                if modified:
+                    inputs_nb_file = outputs_nb_file = None
+                formats = notebook.metadata['jupytext']['formats']
+
+                for ipynb in [True, False]:
+                    # Write first format last so that it is the most recent file
+                    for alt_path, alt_fmt in paired_paths(nb_file, fmt, formats)[::-1]:
+                        # Write ipynb first for compatibility with our contents manager
+                        if alt_path.endswith('.ipynb') != ipynb:
+                            continue
+                        # Do not write the ipynb file if it was not modified
+                        # But, always write text representations to make sure they are the most recent
+                        if alt_path == inputs_nb_file and alt_path == outputs_nb_file:
+                            continue
+                        log("[jupytext] Updating '{}'".format(alt_path))
+                        writef_git_add(notebook, alt_path, alt_fmt)
+        except (ValueError, TypeError, IOError) as err:
+            if args.warn_only:
+                sys.stderr.write('[jupytext] Error: {}\n'.format(str(err)))
             else:
-                action = ''
-
-            log('[jupytext] Writing {nb_dest}{format}{action}'
-                .format(nb_dest=nb_dest,
-                        format=' in format ' + short_form_one_format(
-                            dest_fmt) if dest_fmt and 'format_name' in dest_fmt else '',
-                        action=action))
-            writef_git_add(notebook, nb_dest, dest_fmt)
-
-        # c. Synchronize paired notebooks
-        if args.sync:
-            # Also update the original notebook if the notebook was modified
-            if modified:
-                inputs_nb_file = outputs_nb_file = None
-            formats = notebook.metadata['jupytext']['formats']
-
-            for ipynb in [True, False]:
-                # Write first format last so that it is the most recent file
-                for alt_path, alt_fmt in paired_paths(nb_file, fmt, formats)[::-1]:
-                    # Write ipynb first for compatibility with our contents manager
-                    if alt_path.endswith('.ipynb') != ipynb:
-                        continue
-                    # Do not write the ipynb file if it was not modified
-                    # But, always write text representations to make sure they are the most recent
-                    if alt_path == inputs_nb_file and alt_path == outputs_nb_file:
-                        continue
-                    log("[jupytext] Updating '{}'".format(alt_path))
-                    writef_git_add(notebook, alt_path, alt_fmt)
+                raise err
 
     return round_trip_conversion_errors
 
@@ -396,7 +406,7 @@ def notebooks_in_git_index(fmt):
 
 def print_paired_paths(nb_file, fmt):
     """Display the paired paths for this notebook"""
-    notebook = readf(nb_file, fmt)
+    notebook = read(nb_file, fmt=fmt)
     formats = notebook.metadata.get('jupytext', {}).get('formats')
     if formats:
         for path, _ in paired_paths(nb_file, fmt, formats):
@@ -464,16 +474,16 @@ def load_paired_notebook(notebook, fmt, nb_file, log):
 
     if latest_outputs and latest_outputs != latest_inputs:
         log("[jupytext] Loading input cells from '{}'".format(latest_inputs))
-        inputs = notebook if latest_inputs == nb_file else readf(latest_inputs, input_fmt)
+        inputs = notebook if latest_inputs == nb_file else read(latest_inputs, fmt=input_fmt)
         check_file_version(inputs, latest_inputs, latest_outputs)
         log("[jupytext] Loading output cells from '{}'".format(latest_outputs))
-        outputs = notebook if latest_outputs == nb_file else readf(latest_outputs)
-        combine_inputs_with_outputs(inputs, outputs, input_fmt)
+        outputs = notebook if latest_outputs == nb_file else read(latest_outputs)
+        combine_inputs_with_outputs(inputs, outputs, fmt=input_fmt)
         return inputs, latest_inputs, latest_outputs
 
     log("[jupytext] Loading notebook from '{}'".format(latest_inputs))
     if latest_inputs != nb_file:
-        notebook = readf(latest_inputs, input_fmt)
+        notebook = read(latest_inputs, fmt=input_fmt)
     return notebook, latest_inputs, latest_outputs
 
 
diff --git a/jupytext/jupytext.py b/jupytext/jupytext.py
index 3b3649f3..f69169b5 100644
--- a/jupytext/jupytext.py
+++ b/jupytext/jupytext.py
@@ -4,7 +4,9 @@ import os
 import io
 import sys
 import logging
+import warnings
 from copy import copy, deepcopy
+from ipython_genutils import py3compat
 from nbformat.v4.rwbase import NotebookReader, NotebookWriter
 from nbformat.v4.nbbase import new_notebook, new_code_cell, NotebookNode
 import nbformat
@@ -218,29 +220,27 @@ def reads(text, fmt, as_version=4, **kwargs):
     return notebook
 
 
-def read(file_or_stream, fmt, as_version=4, **kwargs):
+def read(fp, as_version=4, fmt=None, **kwargs):
     """Read a notebook from a file"""
+    if fp == '-':
+        text = sys.stdin.read()
+        fmt = fmt or divine_format(text)
+        return reads(text, fmt)
+
+    if isinstance(fp, (py3compat.unicode_type, bytes)):
+        _, ext = os.path.splitext(fp)
+        fmt = copy(fmt or {})
+        fmt.update({'extension': ext})
+        with io.open(fp, encoding='utf-8') as stream:
+            return read(stream, as_version=as_version, fmt=fmt, **kwargs)
+
     fmt = long_form_one_format(fmt)
     if fmt['extension'] == '.ipynb':
-        notebook = nbformat.read(file_or_stream, as_version, **kwargs)
+        notebook = nbformat.read(fp, as_version, **kwargs)
         rearrange_jupytext_metadata(notebook.metadata)
         return notebook
 
-    return reads(file_or_stream.read(), fmt, **kwargs)
-
-
-def readf(nb_file, fmt=None):
-    """Read a notebook from the file with given name"""
-    if nb_file == '-':
-        text = sys.stdin.read()
-        fmt = fmt or divine_format(text)
-        return reads(text, fmt)
-
-    _, ext = os.path.splitext(nb_file)
-    fmt = copy(fmt or {})
-    fmt.update({'extension': ext})
-    with io.open(nb_file, encoding='utf-8') as stream:
-        return read(stream, fmt, as_version=4)
+    return reads(fp.read(), fmt, **kwargs)
 
 
 def writes(notebook, fmt, version=nbformat.NO_CONVERT, **kwargs):
@@ -277,30 +277,30 @@ def writes(notebook, fmt, version=nbformat.NO_CONVERT, **kwargs):
     return writer.writes(notebook, metadata)
 
 
-def write(notebook, file_or_stream, fmt, version=nbformat.NO_CONVERT, **kwargs):
+def write(nb, fp, version=nbformat.NO_CONVERT, fmt=None, **kwargs):
     """Write a notebook to a file"""
-    # Python 2 compatibility
-    text = u'' + writes(notebook, fmt, version, **kwargs)
-    file_or_stream.write(text)
-    # Add final newline #165
-    if not text.endswith(u'\n'):
-        file_or_stream.write(u'\n')
-
-
-def writef(notebook, nb_file, fmt=None):
-    """Write a notebook to the file with given name"""
-    if nb_file == '-':
-        write(notebook, sys.stdout, fmt)
+    if fp == '-':
+        write(nb, sys.stdout, version=version, fmt=fmt, **kwargs)
         return
 
-    _, ext = os.path.splitext(nb_file)
-    fmt = copy(fmt or {})
-    fmt = long_form_one_format(fmt, update={'extension': ext})
+    if isinstance(fp, (py3compat.unicode_type, bytes)):
+        fmt = copy(fmt or {})
+        _, ext = os.path.splitext(fp)
+        fmt = long_form_one_format(fmt, update={'extension': ext})
+        create_prefix_dir(fp, fmt)
 
-    create_prefix_dir(nb_file, fmt)
+        with io.open(fp, 'w', encoding='utf-8') as stream:
+            write(nb, stream, version=version, fmt=fmt, **kwargs)
+            return
+    else:
+        assert fmt is not None, "'fmt' argument in jupytext.write is mandatory unless fp is a file name"
 
-    with io.open(nb_file, 'w', encoding='utf-8') as stream:
-        write(notebook, stream, fmt)
+    content = writes(nb, version=version, fmt=fmt, **kwargs)
+    if isinstance(content, bytes):
+        content = content.decode('utf8')
+    fp.write(content)
+    if not content.endswith(u'\n'):
+        fp.write(u'\n')
 
 
 def create_prefix_dir(nb_file, fmt):
@@ -310,3 +310,23 @@ def create_prefix_dir(nb_file, fmt):
         if not os.path.isdir(nb_dir):
             logging.log(logging.WARNING, "[jupytext] creating missing directory %s", nb_dir)
             os.makedirs(nb_dir)
+
+
+def readf(nb_file, fmt=None):
+    """Read a notebook from the file with given name"""
+    warnings.warn(
+        "readf is deprecated. Please use read instead, and pass the fmt "
+        "argument with an explicit fmt=... (see https://github.com/mwouts/jupytext/issues/262)",
+        DeprecationWarning)
+
+    return read(fp=nb_file, fmt=fmt)
+
+
+def writef(notebook, nb_file, fmt=None):
+    """Write a notebook to the file with given name"""
+    warnings.warn(
+        'writef is deprecated. Please use write instead, and pass the fmt '
+        'argument with an explicit fmt=... (see https://github.com/mwouts/jupytext/issues/262)',
+        DeprecationWarning)
+
+    return write(nb=notebook, fp=nb_file, fmt=fmt)
diff --git a/jupytext/version.py b/jupytext/version.py
index 097105b5..9c5b3687 100644
--- a/jupytext/version.py
+++ b/jupytext/version.py
@@ -1,3 +1,3 @@
 """Jupytext's version number"""
 
-__version__ = '1.1.7'
+__version__ = '1.2.0-dev'


Test patch:
diff --git a/tests/test_auto_ext.py b/tests/test_auto_ext.py
index a5853bfd..9d7dbf7b 100644
--- a/tests/test_auto_ext.py
+++ b/tests/test_auto_ext.py
@@ -1,12 +1,12 @@
 import pytest
-from jupytext import readf, reads, writes
+from jupytext import read, reads, writes
 from jupytext.formats import JupytextFormatError, auto_ext_from_metadata
 from .utils import list_notebooks
 
 
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_R') + list_notebooks('ipynb_py'))
 def test_auto_in_fmt(nb_file):
-    nb = readf(nb_file)
+    nb = read(nb_file)
     auto_ext = auto_ext_from_metadata(nb.metadata)
     fmt = auto_ext[1:] + ':percent'
     text = writes(nb, 'auto:percent')
@@ -24,7 +24,7 @@ def test_auto_in_fmt(nb_file):
 
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_R') + list_notebooks('ipynb_py'))
 def test_auto_in_formats(nb_file):
-    nb = readf(nb_file)
+    nb = read(nb_file)
     nb.metadata['jupytext'] = {'formats': 'ipynb,auto:percent'}
     fmt = auto_ext_from_metadata(nb.metadata)[1:] + ':percent'
     expected_formats = 'ipynb,' + fmt
diff --git a/tests/test_black.py b/tests/test_black.py
index 87415506..558e9f04 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -5,7 +5,7 @@ from testfixtures import compare
 from nbformat.v4.nbbase import new_notebook, new_code_cell
 from .utils import list_notebooks, requires_black, requires_flake8, requires_autopep8
 
-from jupytext import readf, writef
+from jupytext import read, write
 from jupytext.cli import system, jupytext, pipe_notebook
 from jupytext.combine import black_invariant
 from jupytext.header import _DEFAULT_NOTEBOOK_METADATA
@@ -22,9 +22,9 @@ def test_apply_black_on_python_notebooks(nb_file, tmpdir):
     system('black', tmp_py)
     jupytext(args=[tmp_py, '--to', 'ipynb', '--update'])
 
-    nb1 = readf(nb_file)
-    nb2 = readf(tmp_ipynb)
-    nb3 = readf(tmp_py)
+    nb1 = read(nb_file)
+    nb2 = read(tmp_ipynb)
+    nb3 = read(tmp_py)
 
     assert len(nb1.cells) == len(nb2.cells)
     assert len(nb1.cells) == len(nb3.cells)
@@ -87,7 +87,7 @@ def test_pipe_into_flake8():
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py')[:1])
 def test_apply_black_through_jupytext(tmpdir, nb_file):
     # Load real notebook metadata to get the 'auto' extension in --pipe-fmt to work
-    metadata = readf(nb_file).metadata
+    metadata = read(nb_file).metadata
 
     nb_org = new_notebook(cells=[new_code_cell('1        +1')], metadata=metadata)
     nb_black = new_notebook(cells=[new_code_cell('1 + 1')], metadata=metadata)
@@ -99,27 +99,27 @@ def test_apply_black_through_jupytext(tmpdir, nb_file):
     tmp_py = str(tmpdir.join('script_folder').join('notebook.py'))
 
     # Black in place
-    writef(nb_org, tmp_ipynb)
+    write(nb_org, tmp_ipynb)
     jupytext([tmp_ipynb, '--pipe', 'black'])
-    nb_now = readf(tmp_ipynb)
+    nb_now = read(tmp_ipynb)
     compare(nb_black, nb_now)
 
     # Write to another folder using dots
     script_fmt = os.path.join('..', 'script_folder//py:percent')
-    writef(nb_org, tmp_ipynb)
+    write(nb_org, tmp_ipynb)
     jupytext([tmp_ipynb, '--to', script_fmt, '--pipe', 'black'])
     assert os.path.isfile(tmp_py)
-    nb_now = readf(tmp_py)
+    nb_now = read(tmp_py)
     nb_now.metadata = metadata
     compare(nb_black, nb_now)
     os.remove(tmp_py)
 
     # Map to another folder based on file name
-    writef(nb_org, tmp_ipynb)
+    write(nb_org, tmp_ipynb)
     jupytext([tmp_ipynb, '--from', 'notebook_folder//ipynb', '--to', 'script_folder//py:percent',
               '--pipe', 'black', '--check', 'flake8'])
     assert os.path.isfile(tmp_py)
-    nb_now = readf(tmp_py)
+    nb_now = read(tmp_py)
     nb_now.metadata = metadata
     compare(nb_black, nb_now)
 
@@ -128,7 +128,7 @@ def test_apply_black_through_jupytext(tmpdir, nb_file):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py'))
 def test_apply_black_and_sync_on_paired_notebook(tmpdir, nb_file):
     # Load real notebook metadata to get the 'auto' extension in --pipe-fmt to work
-    metadata = readf(nb_file).metadata
+    metadata = read(nb_file).metadata
     metadata['jupytext'] = {'formats': 'ipynb,py'}
     assert 'language_info' in metadata
 
@@ -139,14 +139,14 @@ def test_apply_black_and_sync_on_paired_notebook(tmpdir, nb_file):
     tmp_py = str(tmpdir.join('notebook.py'))
 
     # Black in place
-    writef(nb_org, tmp_ipynb)
+    write(nb_org, tmp_ipynb)
     jupytext([tmp_ipynb, '--pipe', 'black', '--sync'])
 
-    nb_now = readf(tmp_ipynb)
+    nb_now = read(tmp_ipynb)
     compare(nb_black, nb_now)
     assert 'language_info' in nb_now.metadata
 
-    nb_now = readf(tmp_py)
+    nb_now = read(tmp_py)
     nb_now.metadata['jupytext'].pop('text_representation')
     nb_black.metadata = {key: nb_black.metadata[key] for key in nb_black.metadata
                          if key in _DEFAULT_NOTEBOOK_METADATA}
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6e3169b9..84c44ad2 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -12,7 +12,7 @@ from argparse import ArgumentTypeError
 from nbformat.v4.nbbase import new_notebook, new_markdown_cell, new_code_cell
 from jupyter_client.kernelspec import find_kernel_specs, get_kernel_spec
 from jupytext import __version__
-from jupytext import readf, writef, writes
+from jupytext import read, write, writes
 from jupytext.cli import parse_jupytext_args, jupytext, jupytext_cli, system, str2bool
 from jupytext.compare import compare_notebooks
 from jupytext.paired_paths import paired_paths
@@ -50,8 +50,8 @@ def test_convert_single_file_in_place(nb_file, tmpdir):
 
     jupytext([nb_org, '--to', 'py'])
 
-    nb1 = readf(nb_org)
-    nb2 = readf(nb_other)
+    nb1 = read(nb_org)
+    nb2 = read(nb_other)
 
     compare_notebooks(nb1, nb2)
 
@@ -61,7 +61,7 @@ def test_convert_single_file(nb_file, tmpdir, capsys):
     nb_org = str(tmpdir.join(os.path.basename(nb_file)))
     copyfile(nb_file, nb_org)
 
-    nb1 = readf(nb_file)
+    nb1 = read(nb_file)
     pynb = writes(nb1, 'py')
     jupytext([nb_org, '--to', 'py', '-o', '-'])
 
@@ -85,8 +85,8 @@ def test_wildcard(tmpdir):
     nb1_py = str(tmpdir.join('nb1.py'))
     nb2_py = str(tmpdir.join('nb2.py'))
 
-    writef(new_notebook(metadata={'notebook': 1}), nb1_ipynb)
-    writef(new_notebook(metadata={'notebook': 2}), nb2_ipynb)
+    write(new_notebook(metadata={'notebook': 1}), nb1_ipynb)
+    write(new_notebook(metadata={'notebook': 2}), nb2_ipynb)
 
     os.chdir(str(tmpdir))
     jupytext(['nb*.ipynb', '--to', 'py'])
@@ -102,7 +102,7 @@ def test_wildcard(tmpdir):
 def test_to_cpluplus(nb_file, tmpdir, capsys):
     nb_org = str(tmpdir.join(os.path.basename(nb_file)))
     copyfile(nb_file, nb_org)
-    nb1 = readf(nb_org)
+    nb1 = read(nb_org)
 
     text_cpp = writes(nb1, 'cpp')
     jupytext([nb_org, '--to', 'c++', '--output', '-'])
@@ -128,8 +128,8 @@ def test_convert_multiple_file(nb_files, tmpdir):
     jupytext(nb_orgs + ['--to', 'py'])
 
     for nb_org, nb_other in zip(nb_orgs, nb_others):
-        nb1 = readf(nb_org)
-        nb2 = readf(nb_other)
+        nb1 = read(nb_org)
+        nb2 = read(nb_other)
         compare_notebooks(nb1, nb2)
 
 
@@ -147,7 +147,7 @@ def test_error_not_notebook_ext_input(tmpdir, capsys):
 @pytest.fixture
 def tmp_ipynb(tmpdir):
     tmp_file = str(tmpdir.join('notebook.ipynb'))
-    writef(new_notebook(), tmp_file)
+    write(new_notebook(), tmp_file)
     return tmp_file
 
 
@@ -231,7 +231,7 @@ def test_combine_same_version_ok(tmpdir):
 """)
 
     nb = new_notebook(metadata={'jupytext_formats': 'ipynb,py'})
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     # to jupyter notebook
     jupytext([tmp_nbpy, '--to', 'ipynb', '--update'])
@@ -240,13 +240,13 @@ def test_combine_same_version_ok(tmpdir):
     # test ipynb to rmd
     jupytext([tmp_ipynb, '--to', 'rmarkdown'])
 
-    nb = readf(tmp_ipynb)
+    nb = read(tmp_ipynb)
     cells = nb['cells']
     assert len(cells) == 1
     assert cells[0].cell_type == 'markdown'
     assert cells[0].source == 'New cell'
 
-    nb = readf(tmp_rmd)
+    nb = read(tmp_rmd)
     cells = nb['cells']
     assert len(cells) == 1
     assert cells[0].cell_type == 'markdown'
@@ -268,7 +268,7 @@ def test_combine_lower_version_raises(tmpdir):
 """)
 
     nb = new_notebook(metadata={'jupytext_formats': 'ipynb,py'})
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     with pytest.raises(ValueError) as info:
         jupytext([tmp_nbpy, '--to', 'ipynb', '--update'])
@@ -301,8 +301,8 @@ def test_convert_to_percent_format(nb_file, tmpdir):
         py_script = stream.read()
         assert 'format_name: percent' in py_script
 
-    nb1 = readf(tmp_ipynb)
-    nb2 = readf(tmp_nbpy)
+    nb1 = read(tmp_ipynb)
+    nb2 = read(tmp_nbpy)
 
     compare_notebooks(nb1, nb2)
 
@@ -322,8 +322,8 @@ def test_convert_to_percent_format_and_keep_magics(nb_file, tmpdir):
         assert 'comment_magics: false' in py_script
         assert '# %%time' not in py_script
 
-    nb1 = readf(tmp_ipynb)
-    nb2 = readf(tmp_nbpy)
+    nb1 = read(tmp_ipynb)
+    nb2 = read(tmp_nbpy)
 
     compare_notebooks(nb1, nb2)
 
@@ -357,7 +357,7 @@ def test_pre_commit_hook(tmpdir):
     st = os.stat(hook)
     os.chmod(hook, st.st_mode | stat.S_IEXEC)
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     assert os.path.isfile(tmp_ipynb)
     assert not os.path.isfile(tmp_py)
 
@@ -388,7 +388,7 @@ def test_pre_commit_hook_in_subfolder(tmpdir):
     st = os.stat(hook)
     os.chmod(hook, st.st_mode | stat.S_IEXEC)
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     assert os.path.isfile(tmp_ipynb)
     assert not os.path.isfile(tmp_py)
 
@@ -421,7 +421,7 @@ def test_pre_commit_hook_py_to_ipynb_and_md(tmpdir):
     st = os.stat(hook)
     os.chmod(hook, st.st_mode | stat.S_IEXEC)
 
-    writef(nb, tmp_py)
+    write(nb, tmp_py)
     assert os.path.isfile(tmp_py)
     assert not os.path.isfile(tmp_ipynb)
     assert not os.path.isfile(tmp_md)
@@ -443,7 +443,7 @@ def test_pre_commit_hook_py_to_ipynb_and_md(tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py')[:1])
 def test_pre_commit_hook_sync_black_flake8(tmpdir, nb_file):
     # Load real notebook metadata to get the 'auto' extension in --pipe-fmt to work
-    metadata = readf(nb_file).metadata
+    metadata = read(nb_file).metadata
 
     git = git_in_tmpdir(tmpdir)
     git('init')
@@ -463,7 +463,7 @@ def test_pre_commit_hook_sync_black_flake8(tmpdir, nb_file):
     tmp_py = str(tmpdir.join('notebook.py'))
     nb = new_notebook(cells=[new_code_cell(source='1+    1')], metadata=metadata)
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     git('add', 'notebook.ipynb')
     git('status')
     git('commit', '-m', 'created')
@@ -474,7 +474,7 @@ def test_pre_commit_hook_sync_black_flake8(tmpdir, nb_file):
         assert fp.read().splitlines()[-1] == '1 + 1'
 
     nb = new_notebook(cells=[new_code_cell(source='"""trailing   \nwhitespace"""')], metadata=metadata)
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     git('add', 'notebook.ipynb')
     git('status')
     with pytest.raises(SystemExit):  # not flake8
@@ -498,7 +498,7 @@ def test_manual_call_of_pre_commit_hook(tmpdir):
         with mock.patch('jupytext.cli.system', system_in_tmpdir):
             jupytext(['--to', 'py', '--pre-commit'])
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     assert os.path.isfile(tmp_ipynb)
     assert not os.path.isfile(tmp_py)
 
@@ -521,7 +521,7 @@ def test_set_formats(py_file, tmpdir):
 
     jupytext(['--to', 'ipynb', tmp_py, '--set-formats', 'ipynb,py:light'])
 
-    nb = readf(tmp_ipynb)
+    nb = read(tmp_ipynb)
     assert nb.metadata['jupytext']['formats'] == 'ipynb,py:light'
 
 
@@ -530,7 +530,7 @@ def test_set_formats_py_file_only(py_file, tmpdir):
     tmp_py = str(tmpdir.join('notebook.py'))
     copyfile(py_file, tmp_py)
     jupytext([tmp_py, '--set-formats', 'ipynb,py:light'])
-    nb = readf(tmp_py)
+    nb = read(tmp_py)
     assert nb.metadata['jupytext']['formats'] == 'ipynb,py:light'
 
 
@@ -543,12 +543,12 @@ def test_update_metadata(py_file, tmpdir, capsys):
 
     jupytext(['--to', 'ipynb', tmp_py, '--update-metadata', '{"jupytext":{"formats":"ipynb,py:light"}}'])
 
-    nb = readf(tmp_ipynb)
+    nb = read(tmp_ipynb)
     assert nb.metadata['jupytext']['formats'] == 'ipynb,py:light'
 
     jupytext(['--to', 'py', tmp_ipynb, '--update-metadata', '{"jupytext":{"formats":null}}'])
 
-    nb = readf(tmp_py)
+    nb = read(tmp_py)
     assert 'formats' not in nb.metadata['jupytext']
 
     with pytest.raises(SystemExit):
@@ -566,7 +566,7 @@ def test_set_kernel_inplace(py_file, tmpdir):
 
     jupytext([tmp_py, '--set-kernel', '-'])
 
-    nb = readf(tmp_py)
+    nb = read(tmp_py)
     kernel_name = nb.metadata['kernelspec']['name']
     assert get_kernel_spec(kernel_name).argv[0] in ['python', sys.executable]
 
@@ -580,7 +580,7 @@ def test_set_kernel_auto(py_file, tmpdir):
 
     jupytext(['--to', 'ipynb', tmp_py, '--set-kernel', '-'])
 
-    nb = readf(tmp_ipynb)
+    nb = read(tmp_ipynb)
     kernel_name = nb.metadata['kernelspec']['name']
     assert get_kernel_spec(kernel_name).argv[0] in ['python', sys.executable]
 
@@ -595,7 +595,7 @@ def test_set_kernel_with_name(py_file, tmpdir):
     for kernel in find_kernel_specs():
         jupytext(['--to', 'ipynb', tmp_py, '--set-kernel', kernel])
 
-        nb = readf(tmp_ipynb)
+        nb = read(tmp_ipynb)
         assert nb.metadata['kernelspec']['name'] == kernel
 
     with pytest.raises(KeyError):
@@ -605,9 +605,9 @@ def test_set_kernel_with_name(py_file, tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py'))
 def test_paired_paths(nb_file, tmpdir, capsys):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
-    nb = readf(nb_file)
+    nb = read(nb_file)
     nb.metadata.setdefault('jupytext', {})['formats'] = 'ipynb,_light.py,_percent.py:percent'
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     jupytext(['--paired-paths', tmp_ipynb])
 
@@ -624,8 +624,8 @@ def test_sync(nb_file, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_py = str(tmpdir.join('notebook.py'))
     tmp_rmd = str(tmpdir.join('notebook.Rmd'))
-    nb = readf(nb_file)
-    writef(nb, tmp_ipynb)
+    nb = read(nb_file)
+    write(nb, tmp_ipynb)
 
     # Test that sync fails when notebook is not paired
     with pytest.raises(ValueError) as info:
@@ -634,27 +634,27 @@ def test_sync(nb_file, tmpdir):
 
     # Now with a pairing information
     nb.metadata.setdefault('jupytext', {})['formats'] = 'py,Rmd,ipynb'
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     # Test that missing files are created
     jupytext(['--sync', tmp_ipynb])
 
     assert os.path.isfile(tmp_py)
-    compare_notebooks(nb, readf(tmp_py))
+    compare_notebooks(nb, read(tmp_py))
 
     assert os.path.isfile(tmp_rmd)
-    compare_notebooks(nb, readf(tmp_rmd), 'Rmd')
+    compare_notebooks(nb, read(tmp_rmd), 'Rmd')
 
-    writef(nb, tmp_rmd, 'Rmd')
+    write(nb, tmp_rmd, 'Rmd')
     jupytext(['--sync', tmp_ipynb])
 
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2, 'Rmd', compare_outputs=True)
 
-    writef(nb, tmp_py, 'py')
+    write(nb, tmp_py, 'py')
     jupytext(['--sync', tmp_ipynb])
 
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2, compare_outputs=True)
 
     # Finally we recreate the ipynb
@@ -663,7 +663,7 @@ def test_sync(nb_file, tmpdir):
     time.sleep(0.1)
     jupytext(['--sync', tmp_py])
 
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2)
 
     # ipynb must be older than py file, otherwise our Contents Manager will complain
@@ -675,8 +675,8 @@ def test_sync(nb_file, tmpdir):
 def test_sync_pandoc(nb_file, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_md = str(tmpdir.join('notebook.md'))
-    nb = readf(nb_file)
-    writef(nb, tmp_ipynb)
+    nb = read(nb_file)
+    write(nb, tmp_ipynb)
 
     # Test that sync fails when notebook is not paired
     with pytest.raises(ValueError) as info:
@@ -685,13 +685,13 @@ def test_sync_pandoc(nb_file, tmpdir):
 
     # Now with a pairing information
     nb.metadata.setdefault('jupytext', {})['formats'] = 'ipynb,md:pandoc'
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     # Test that missing files are created
     jupytext(['--sync', tmp_ipynb])
 
     assert os.path.isfile(tmp_md)
-    compare_notebooks(nb, readf(tmp_md), 'md:pandoc')
+    compare_notebooks(nb, read(tmp_md), 'md:pandoc')
 
     with open(tmp_md) as fp:
         assert 'pandoc' in fp.read()
@@ -704,18 +704,18 @@ def test_sync_pandoc(nb_file, tmpdir):
 def test_cli_can_infer_jupytext_format(nb_file, ext, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_text = str(tmpdir.join('notebook' + ext))
-    nb = readf(nb_file)
+    nb = read(nb_file)
 
     # Light format to Jupyter notebook
-    writef(nb, tmp_text)
+    write(nb, tmp_text)
     jupytext(['--to', 'notebook', tmp_text])
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2)
 
     # Percent format to Jupyter notebook
-    writef(nb, tmp_text, ext + ':percent')
+    write(nb, tmp_text, ext + ':percent')
     jupytext(['--to', 'notebook', tmp_text])
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2)
 
 
@@ -725,11 +725,11 @@ def test_cli_can_infer_jupytext_format(nb_file, ext, tmpdir):
 def test_cli_to_script(nb_file, ext, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_text = str(tmpdir.join('notebook' + ext))
-    nb = readf(nb_file)
+    nb = read(nb_file)
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     jupytext(['--to', 'script', tmp_ipynb])
-    nb2 = readf(tmp_text)
+    nb2 = read(tmp_text)
     compare_notebooks(nb, nb2)
 
 
@@ -739,11 +739,11 @@ def test_cli_to_script(nb_file, ext, tmpdir):
 def test_cli_to_auto(nb_file, ext, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_text = str(tmpdir.join('notebook' + ext))
-    nb = readf(nb_file)
+    nb = read(nb_file)
 
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
     jupytext(['--to', 'auto', tmp_ipynb])
-    nb2 = readf(tmp_text)
+    nb2 = read(tmp_text)
     compare_notebooks(nb, nb2)
 
 
@@ -752,30 +752,30 @@ def test_cli_can_infer_jupytext_format_from_stdin(nb_file, tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
     tmp_py = str(tmpdir.join('notebook.py'))
     tmp_rmd = str(tmpdir.join('notebook.Rmd'))
-    nb = readf(nb_file)
+    nb = read(nb_file)
 
     # read ipynb notebook on stdin, write to python
     with open(nb_file) as fp, mock.patch('sys.stdin', fp):
         jupytext(['--to', 'py:percent', '-o', tmp_py])
-    nb2 = readf(tmp_py)
+    nb2 = read(tmp_py)
     compare_notebooks(nb, nb2)
 
     # read python notebook on stdin, write to ipynb
     with open(tmp_py) as fp, mock.patch('sys.stdin', fp):
         jupytext(['-o', tmp_ipynb])
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2)
 
     # read ipynb notebook on stdin, write to R markdown
     with open(nb_file) as fp, mock.patch('sys.stdin', fp):
         jupytext(['-o', tmp_rmd])
-    nb2 = readf(tmp_rmd)
+    nb2 = read(tmp_rmd)
     compare_notebooks(nb, nb2, 'Rmd')
 
     # read markdown notebook on stdin, write to ipynb
     with open(tmp_rmd) as fp, mock.patch('sys.stdin', fp):
         jupytext(['-o', tmp_ipynb])
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     compare_notebooks(nb, nb2, 'Rmd')
 
 
@@ -801,7 +801,7 @@ def test_format_prefix_suffix(tmpdir):
     os.makedirs(str(tmpdir.join('notebooks')))
     tmp_ipynb = str(tmpdir.join('notebooks/notebook_name.ipynb'))
     tmp_py = str(tmpdir.join('scripts/notebook_name.py'))
-    writef(new_notebook(), tmp_ipynb)
+    write(new_notebook(), tmp_ipynb)
 
     jupytext([tmp_ipynb, '--to', os.path.join('..', 'scripts//py')])
     assert os.path.isfile(tmp_py)
@@ -813,7 +813,7 @@ def test_format_prefix_suffix(tmpdir):
 
     tmp_ipynb = str(tmpdir.join('notebooks/nb_prefix_notebook_name.ipynb'))
     tmp_py = str(tmpdir.join('scripts/script_prefix_notebook_name.py'))
-    writef(new_notebook(), tmp_ipynb)
+    write(new_notebook(), tmp_ipynb)
 
     jupytext([tmp_ipynb, '--to', 'scripts/script_prefix_/py', '--from', 'notebooks/nb_prefix_/ipynb'])
     assert os.path.isfile(tmp_py)
@@ -821,7 +821,7 @@ def test_format_prefix_suffix(tmpdir):
 
     tmp_ipynb = str(tmpdir.join('notebooks/nb_prefix_notebook_name_nb_suffix.ipynb'))
     tmp_py = str(tmpdir.join('scripts/script_prefix_notebook_name_script_suffix.py'))
-    writef(new_notebook(), tmp_ipynb)
+    write(new_notebook(), tmp_ipynb)
 
     jupytext([tmp_ipynb, '--to', 'scripts/script_prefix_/_script_suffix.py',
               '--from', 'notebooks/nb_prefix_/_nb_suffix.ipynb'])
@@ -837,7 +837,7 @@ def test_cli_sync_file_with_suffix(tmpdir):
     nb = new_notebook(cells=[new_code_cell(source='1+1')],
                       metadata={'jupytext': {'formats': 'ipynb,.pct.py:percent,.lgt.py:light,Rmd'}})
 
-    writef(nb, tmp_pct_py, '.pct.py:percent')
+    write(nb, tmp_pct_py, '.pct.py:percent')
     jupytext(['--sync', tmp_pct_py])
     assert os.path.isfile(tmp_lgt_py)
     assert os.path.isfile(tmp_rmd)
@@ -859,13 +859,13 @@ def test_rst2md(tmpdir):
     # Write notebook in sphinx format
     nb = new_notebook(cells=[new_markdown_cell('A short sphinx notebook'),
                              new_markdown_cell(':math:`1+1`')])
-    writef(nb, tmp_py, 'py:sphinx')
+    write(nb, tmp_py, fmt='py:sphinx')
 
     jupytext([tmp_py, '--from', 'py:sphinx', '--to', 'ipynb',
               '--opt', 'rst2md=True', '--opt', 'cell_metadata_filter=-all'])
 
     assert os.path.isfile(tmp_ipynb)
-    nb = readf(tmp_ipynb)
+    nb = read(tmp_ipynb)
 
     assert nb.metadata['jupytext']['cell_metadata_filter'] == '-all'
     assert nb.metadata['jupytext']['rst2md'] is False
@@ -888,13 +888,13 @@ def test_remove_jupytext_metadata(tmpdir):
     nbformat.write(nb, tmp_ipynb, version=nbformat.NO_CONVERT)
     # Jupytext removes the 'text_representation' information from the notebook
     jupytext([tmp_ipynb, '--update-metadata', '{"jupytext":{"main_language":null}}'])
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     assert not nb2.metadata
 
     nbformat.write(nb, tmp_ipynb, version=nbformat.NO_CONVERT)
     jupytext([tmp_ipynb, '--set-formats', 'ipynb,py:light'])
 
-    nb2 = readf(tmp_ipynb)
+    nb2 = read(tmp_ipynb)
     assert nb2.metadata == {'jupytext': {'formats': 'ipynb,py:light', 'main_language': 'python'}}
 
 
@@ -912,6 +912,46 @@ def test_convert_and_update_preserves_notebook(nb_file, fmt, tmpdir):
     jupytext(['--to', fmt, tmp_ipynb])
     jupytext(['--to', 'ipynb', '--update', tmp_text])
 
-    nb_org = readf(nb_file)
-    nb_now = readf(tmp_ipynb)
+    nb_org = read(nb_file)
+    nb_now = read(tmp_ipynb)
     compare(nb_org, nb_now)
+
+
+def test_incorrect_notebook_causes_early_exit(tmpdir):
+    incorrect_ipynb = str(tmpdir.join('incorrect.ipynb'))
+    incorrect_md = str(tmpdir.join('incorrect.md'))
+    with open(incorrect_ipynb, 'w') as fp:
+        fp.write('{"nbformat": 4, "nbformat_minor": 2, "metadata": {INCORRECT}, "cells": []}')
+
+    correct_ipynb = str(tmpdir.join('correct.ipynb'))
+    correct_md = str(tmpdir.join('correct.md'))
+    with open(correct_ipynb, 'w') as fp:
+        fp.write('{"nbformat": 4, "nbformat_minor": 2, "metadata": {}, "cells": []}')
+
+    with pytest.raises(nbformat.reader.NotJSONError) as error:
+        jupytext([incorrect_ipynb, correct_ipynb, '--to', 'md'])
+
+    assert "Notebook does not appear to be JSON" in str(error)
+
+    assert not os.path.exists(incorrect_md)
+    assert not os.path.exists(correct_md)
+
+
+def test_warn_only_skips_incorrect_notebook(tmpdir, capsys):
+    incorrect_ipynb = str(tmpdir.join('incorrect.ipynb'))
+    incorrect_md = str(tmpdir.join('incorrect.md'))
+    with open(incorrect_ipynb, 'w') as fp:
+        fp.write('{"nbformat": 4, "nbformat_minor": 2, "metadata": {INCORRECT}, "cells": []}')
+
+    correct_ipynb = str(tmpdir.join('correct.ipynb'))
+    correct_md = str(tmpdir.join('correct.md'))
+    with open(correct_ipynb, 'w') as fp:
+        fp.write('{"nbformat": 4, "nbformat_minor": 2, "metadata": {}, "cells": []}')
+
+    jupytext([incorrect_ipynb, correct_ipynb, '--to', 'md', '--warn-only'])
+
+    _, err = capsys.readouterr()
+    assert "Notebook does not appear to be JSON" in str(err)
+
+    assert not os.path.exists(incorrect_md)
+    assert os.path.exists(correct_md)
diff --git a/tests/test_contentsmanager.py b/tests/test_contentsmanager.py
index 4304bb8b..17152c7f 100644
--- a/tests/test_contentsmanager.py
+++ b/tests/test_contentsmanager.py
@@ -10,7 +10,7 @@ from nbformat.v4.nbbase import new_notebook, new_markdown_cell, new_code_cell
 from tornado.web import HTTPError
 from testfixtures import compare
 import jupytext
-from jupytext.jupytext import writes, writef, readf
+from jupytext.jupytext import writes, write, read
 from jupytext.compare import compare_notebooks
 from jupytext.header import header_to_metadata_and_cell
 from jupytext.formats import read_format_from_metadata, auto_ext_from_metadata
@@ -25,7 +25,7 @@ def test_create_contentsmanager():
 def test_rename(tmpdir):
     org_file = str(tmpdir.join('notebook.ipynb'))
     new_file = str(tmpdir.join('new.ipynb'))
-    jupytext.writef(new_notebook(), org_file)
+    jupytext.write(new_notebook(), org_file)
 
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
@@ -38,7 +38,7 @@ def test_rename(tmpdir):
 def test_rename_inconsistent_path(tmpdir):
     org_file = str(tmpdir.join('notebook_suffix.ipynb'))
     new_file = str(tmpdir.join('new.ipynb'))
-    jupytext.writef(new_notebook(metadata={'jupytext': {'formats': '_suffix.ipynb'}}), org_file)
+    jupytext.write(new_notebook(metadata={'jupytext': {'formats': '_suffix.ipynb'}}), org_file)
 
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
@@ -62,7 +62,7 @@ def test_load_save_rename(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save Rmd, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     cm.save(model=dict(type='notebook', content=nb), path=tmp_rmd)
     nb_rmd = cm.get(tmp_rmd)
     compare_notebooks(nb, nb_rmd['content'], 'Rmd')
@@ -105,7 +105,7 @@ def test_save_load_paired_md_notebook(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save with cm, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb.metadata['jupytext'] = {'formats': 'ipynb,md'}
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
@@ -126,7 +126,7 @@ def test_save_load_paired_md_pandoc_notebook(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save with cm, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb.metadata['jupytext'] = {'formats': 'ipynb,md:pandoc'}
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
@@ -146,7 +146,7 @@ def test_pair_plain_script(py_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open py file, pair, save with cm
-    nb = jupytext.readf(py_file)
+    nb = jupytext.read(py_file)
     nb.metadata['jupytext']['formats'] = 'ipynb,py:hydrogen'
     cm.save(model=dict(type='notebook', content=nb), path=tmp_py)
 
@@ -188,7 +188,7 @@ def test_load_save_rename_nbpy(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save nb.py, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy)
     nbpy = cm.get(tmp_nbpy)
     compare_notebooks(nb, nbpy['content'])
@@ -246,7 +246,7 @@ def test_load_save_rename_notebook_with_dot(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save nb.py, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy)
     nbpy = cm.get(tmp_nbpy)
     compare_notebooks(nb, nbpy['content'])
@@ -274,7 +274,7 @@ def test_load_save_rename_nbpy_default_config(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save nb.py, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy)
     nbpy = cm.get(tmp_nbpy)
@@ -315,7 +315,7 @@ def test_load_save_rename_non_ascii_path(nb_file, tmpdir):
     cm.root_dir = tmpdir
 
     # open ipynb, save nb.py, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy)
     nbpy = cm.get(tmp_nbpy)
@@ -357,7 +357,7 @@ def test_outdated_text_notebook(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # open ipynb, save py, reopen
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy)
     model_py = cm.get(tmp_nbpy, load_alternative_format=False)
     model_ipynb = cm.get(tmp_ipynb, load_alternative_format=False)
@@ -426,7 +426,7 @@ def test_save_to_percent_format(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
     cm.preferred_jupytext_formats_save = 'jl:percent'
 
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb['metadata']['jupytext'] = {'formats': 'ipynb,jl'}
 
     # save to ipynb and jl
@@ -444,7 +444,7 @@ def test_save_to_percent_format(nb_file, tmpdir):
 @skip_if_dict_is_not_ordered
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py'))
 def test_save_using_preferred_and_default_format_170(nb_file, tmpdir):
-    nb = readf(nb_file)
+    nb = read(nb_file)
 
     # Way 0: preferred_jupytext_formats_save, no prefix + default_jupytext_formats
     tmp_py = str(tmpdir.join('python/notebook.py'))
@@ -458,7 +458,7 @@ def test_save_using_preferred_and_default_format_170(nb_file, tmpdir):
     cm.save(model=dict(type='notebook', content=nb), path='notebook.ipynb')
 
     # read py file
-    nb_py = readf(tmp_py)
+    nb_py = read(tmp_py)
     assert nb_py.metadata['jupytext']['text_representation']['format_name'] == 'percent'
 
     # Way 1: preferred_jupytext_formats_save + default_jupytext_formats
@@ -473,7 +473,7 @@ def test_save_using_preferred_and_default_format_170(nb_file, tmpdir):
     cm.save(model=dict(type='notebook', content=nb), path='notebook.ipynb')
 
     # read py file
-    nb_py = readf(tmp_py)
+    nb_py = read(tmp_py)
     assert nb_py.metadata['jupytext']['text_representation']['format_name'] == 'percent'
 
     # Way 2: default_jupytext_formats
@@ -487,7 +487,7 @@ def test_save_using_preferred_and_default_format_170(nb_file, tmpdir):
     cm.save(model=dict(type='notebook', content=nb), path='notebook.ipynb')
 
     # read py file
-    nb_py = readf(tmp_py)
+    nb_py = read(tmp_py)
     assert nb_py.metadata['jupytext']['text_representation']['format_name'] == 'percent'
 
 
@@ -566,7 +566,7 @@ def test_save_to_light_percent_sphinx_format(nb_file, tmpdir):
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
 
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb['metadata']['jupytext'] = {'formats': 'ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx'}
 
     # save to ipynb and three python flavors
@@ -605,7 +605,7 @@ def test_pair_notebook_with_dot(nb_file, tmpdir):
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
 
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb['metadata']['jupytext'] = {'formats': 'ipynb,py:percent'}
 
     # save to ipynb and three python flavors
@@ -637,7 +637,7 @@ def test_preferred_format_allows_to_read_others_format(nb_file, tmpdir):
     cm.root_dir = str(tmpdir)
 
     # load notebook and save it using the cm
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb['metadata']['jupytext'] = {'formats': 'ipynb,py'}
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
 
@@ -691,7 +691,7 @@ def test_preferred_formats_read_auto(tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb'))
 def test_save_in_auto_extension_global(nb_file, tmpdir):
     # load notebook
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     if 'language_info' not in nb.metadata:
         return
 
@@ -780,7 +780,7 @@ def test_global_auto_pairing_works_with_empty_notebook(tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb'))
 def test_save_in_auto_extension_global_with_format(nb_file, tmpdir):
     # load notebook
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     if 'language_info' not in nb.metadata:
         return
 
@@ -812,7 +812,7 @@ def test_save_in_auto_extension_global_with_format(nb_file, tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb'))
 def test_save_in_auto_extension_local(nb_file, tmpdir):
     # load notebook
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb.metadata.setdefault('jupytext', {})['formats'] = 'ipynb,auto:percent'
     if 'language_info' not in nb.metadata:
         return
@@ -841,7 +841,7 @@ def test_save_in_auto_extension_local(nb_file, tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb'))
 def test_save_in_pct_and_lgt_auto_extensions(nb_file, tmpdir):
     # load notebook
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     if 'language_info' not in nb.metadata:
         return
 
@@ -870,7 +870,7 @@ def test_save_in_pct_and_lgt_auto_extensions(nb_file, tmpdir):
 
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb', skip='magic'))
 def test_metadata_filter_is_effective(nb_file, tmpdir):
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     tmp_ipynb = 'notebook.ipynb'
     tmp_script = 'notebook.py'
 
@@ -896,7 +896,7 @@ def test_metadata_filter_is_effective(nb_file, tmpdir):
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
 
     # read text version
-    nb2 = jupytext.readf(str(tmpdir.join(tmp_script)))
+    nb2 = jupytext.read(str(tmpdir.join(tmp_script)))
 
     # test no metadata
     assert set(nb2.metadata.keys()) <= {'jupytext', 'kernelspec'}
@@ -951,7 +951,7 @@ print('hello2')
 @pytest.mark.parametrize('nb_file,ext', itertools.product(list_notebooks('ipynb_py'), ['.py', '.ipynb']))
 def test_local_format_can_deactivate_pairing(nb_file, ext, tmpdir):
     """This is a test for #157: local format can be used to deactivate the global pairing"""
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     nb.metadata['jupytext_formats'] = ext[1:]  # py or ipynb
 
     # create contents manager with default pairing
@@ -980,7 +980,7 @@ def test_local_format_can_deactivate_pairing(nb_file, ext, tmpdir):
 @pytest.mark.parametrize('nb_file', list_notebooks('Rmd'))
 def test_global_pairing_allows_to_save_other_file_types(nb_file, tmpdir):
     """This is a another test for #157: local format can be used to deactivate the global pairing"""
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
 
     # create contents manager with default pairing
     cm = jupytext.TextFileContentsManager()
@@ -1071,7 +1071,7 @@ def test_rst2md_option(tmpdir):
     # Write notebook in sphinx format
     nb = new_notebook(cells=[new_markdown_cell('A short sphinx notebook'),
                              new_markdown_cell(':math:`1+1`')])
-    writef(nb, tmp_py, 'py:sphinx')
+    write(nb, tmp_py, fmt='py:sphinx')
 
     cm = jupytext.TextFileContentsManager()
     cm.sphinx_convert_rst2md = True
@@ -1115,7 +1115,7 @@ def test_load_then_change_formats(tmpdir):
     tmp_ipynb = str(tmpdir.join('nb.ipynb'))
     tmp_py = str(tmpdir.join('nb.py'))
     nb = new_notebook(metadata={'jupytext': {'formats': 'ipynb,py:light'}})
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_ipynb)
 
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
@@ -1125,7 +1125,7 @@ def test_load_then_change_formats(tmpdir):
 
     cm.save(model, path='nb.ipynb')
     assert os.path.isfile(tmp_py)
-    assert readf(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:light'
+    assert read(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:light'
 
     time.sleep(0.5)
     del model['content'].metadata['jupytext']['formats']
@@ -1137,7 +1137,7 @@ def test_load_then_change_formats(tmpdir):
     model['content'].metadata.setdefault('jupytext', {})['formats'] = 'ipynb,py:percent'
     cm.save(model, path='nb.ipynb')
     assert os.path.isfile(tmp_py)
-    assert readf(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:percent'
+    assert read(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:percent'
     os.remove(tmp_py)
 
     del model['content'].metadata['jupytext']['formats']
@@ -1154,13 +1154,13 @@ def test_set_then_change_formats(tmpdir):
 
     cm.save(model=dict(content=nb, type='notebook'), path='nb.ipynb')
     assert os.path.isfile(tmp_py)
-    assert readf(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:light'
+    assert read(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:light'
     os.remove(tmp_py)
 
     nb.metadata['jupytext']['formats'] = 'ipynb,py:percent'
     cm.save(model=dict(content=nb, type='notebook'), path='nb.ipynb')
     assert os.path.isfile(tmp_py)
-    assert readf(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:percent'
+    assert read(tmp_py).metadata['jupytext']['formats'] == 'ipynb,py:percent'
     os.remove(tmp_py)
 
     del nb.metadata['jupytext']['formats']
@@ -1173,7 +1173,7 @@ def test_set_then_change_auto_formats(tmpdir, nb_file):
     tmp_ipynb = str(tmpdir.join('nb.ipynb'))
     tmp_py = str(tmpdir.join('nb.py'))
     tmp_rmd = str(tmpdir.join('nb.Rmd'))
-    nb = new_notebook(metadata=readf(nb_file).metadata)
+    nb = new_notebook(metadata=read(nb_file).metadata)
 
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
@@ -1184,7 +1184,7 @@ def test_set_then_change_auto_formats(tmpdir, nb_file):
     assert 'nb.py' in cm.paired_notebooks
     assert 'nb.auto' not in cm.paired_notebooks
     assert os.path.isfile(tmp_py)
-    assert readf(tmp_ipynb).metadata['jupytext']['formats'] == 'ipynb,py:light'
+    assert read(tmp_ipynb).metadata['jupytext']['formats'] == 'ipynb,py:light'
 
     # Pair ipynb/Rmd and save
     time.sleep(0.5)
@@ -1194,7 +1194,7 @@ def test_set_then_change_auto_formats(tmpdir, nb_file):
     assert 'nb.py' not in cm.paired_notebooks
     assert 'nb.auto' not in cm.paired_notebooks
     assert os.path.isfile(tmp_rmd)
-    assert readf(tmp_ipynb).metadata['jupytext']['formats'] == 'ipynb,Rmd'
+    assert read(tmp_ipynb).metadata['jupytext']['formats'] == 'ipynb,Rmd'
     cm.get('nb.ipynb')
 
     # Unpair and save
@@ -1225,7 +1225,7 @@ def test_share_py_recreate_ipynb(tmpdir, nb_file):
     cm.default_notebook_metadata_filter = "-all"
     cm.default_cell_metadata_filter = "-all"
 
-    nb = readf(nb_file)
+    nb = read(nb_file)
     model_ipynb = cm.save(model=dict(content=nb, type='notebook'), path='nb.ipynb')
 
     assert os.path.isfile(tmp_ipynb)
@@ -1268,7 +1268,7 @@ def test_vim_folding_markers(tmpdir):
     nb2 = cm.get('nb.ipynb')['content']
     compare_notebooks(nb, nb2)
 
-    nb3 = readf(tmp_py)
+    nb3 = read(tmp_py)
     assert nb3.metadata['jupytext']['cell_markers'] == '{{{,}}}'
 
     with open(tmp_py) as fp:
@@ -1315,7 +1315,7 @@ def test_vscode_pycharm_folding_markers(tmpdir):
     nb2 = cm.get('nb.ipynb')['content']
     compare_notebooks(nb, nb2)
 
-    nb3 = readf(tmp_py)
+    nb3 = read(tmp_py)
     assert nb3.metadata['jupytext']['cell_markers'] == 'region,endregion'
 
     with open(tmp_py) as fp:
@@ -1418,9 +1418,9 @@ def test_notebook_extensions(tmpdir):
     tmp_ipynb = str(tmpdir.join('notebook.ipynb'))
 
     nb = new_notebook()
-    writef(nb, tmp_py)
-    writef(nb, tmp_rmd)
-    writef(nb, tmp_ipynb)
+    write(nb, tmp_py)
+    write(nb, tmp_rmd)
+    write(nb, tmp_ipynb)
 
     cm = jupytext.TextFileContentsManager()
     cm.root_dir = str(tmpdir)
diff --git a/tests/test_jupytext_errors.py b/tests/test_jupytext_errors.py
index 24bb15aa..19695723 100644
--- a/tests/test_jupytext_errors.py
+++ b/tests/test_jupytext_errors.py
@@ -8,10 +8,10 @@ def test_read_wrong_ext(tmpdir, nb_file='notebook.ext'):
     nb_file = tmpdir.join(nb_file)
     nb_file.write('{}')
     with pytest.raises(JupytextFormatError):
-        jupytext.readf(str(nb_file))
+        jupytext.read(str(nb_file))
 
 
 def test_write_wrong_ext(tmpdir, nb_file='notebook.ext'):
     nb_file = str(tmpdir.join(nb_file))
     with pytest.raises(JupytextFormatError):
-        jupytext.writef(new_notebook(), nb_file)
+        jupytext.write(new_notebook(), nb_file)
diff --git a/tests/test_knitr_spin.py b/tests/test_knitr_spin.py
index 41eef9ed..b6858991 100644
--- a/tests/test_knitr_spin.py
+++ b/tests/test_knitr_spin.py
@@ -6,7 +6,7 @@ from .utils import list_notebooks, skip_if_dict_is_not_ordered
 @skip_if_dict_is_not_ordered
 @pytest.mark.parametrize('r_file', list_notebooks('R_spin'))
 def test_jupytext_same_as_knitr_spin(r_file, tmpdir):
-    nb = jupytext.readf(r_file)
+    nb = jupytext.read(r_file)
     rmd_jupytext = jupytext.writes(nb, 'Rmd')
 
     # Rmd file generated with spin(hair='R/spin.R', knit=FALSE)
diff --git a/tests/test_load_multiple.py b/tests/test_load_multiple.py
index 7480b07b..3214b013 100644
--- a/tests/test_load_multiple.py
+++ b/tests/test_load_multiple.py
@@ -20,7 +20,7 @@ def test_combine_same_version_ok(tmpdir):
 """)
 
     nb = new_notebook(metadata={'jupytext_formats': 'ipynb,py'})
-    jupytext.writef(nb, str(tmpdir.join(tmp_ipynb)))
+    jupytext.write(nb, str(tmpdir.join(tmp_ipynb)))
 
     cm = jupytext.TextFileContentsManager()
     cm.default_jupytext_formats = 'ipynb,py'
@@ -48,7 +48,7 @@ def test_combine_lower_version_raises(tmpdir):
 """)
 
     nb = new_notebook(metadata={'jupytext_formats': 'ipynb,py'})
-    jupytext.writef(nb, str(tmpdir.join(tmp_ipynb)))
+    jupytext.write(nb, str(tmpdir.join(tmp_ipynb)))
 
     cm = jupytext.TextFileContentsManager()
     cm.default_jupytext_formats = 'ipynb,py'
diff --git a/tests/test_mirror.py b/tests/test_mirror.py
index c13cdcd7..baeeb3dc 100644
--- a/tests/test_mirror.py
+++ b/tests/test_mirror.py
@@ -22,7 +22,7 @@ pytestmark = skip_if_dict_is_not_ordered
 def create_mirror_file_if_missing(mirror_file, notebook, fmt):
     if not os.path.isfile(mirror_file):
         with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', False):
-            jupytext.writef(notebook, mirror_file, fmt)
+            jupytext.write(notebook, mirror_file, fmt=fmt)
 
 
 def test_create_mirror_file_if_missing(tmpdir):
@@ -40,21 +40,21 @@ def assert_conversion_same_as_mirror(nb_file, fmt, mirror_name, compare_notebook
     mirror_file = os.path.join(dirname, '..', 'mirror', mirror_name, full_path(file_name, fmt))
 
     with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', False):
-        notebook = jupytext.readf(nb_file, fmt)
+        notebook = jupytext.read(nb_file, fmt=fmt)
         # it's better not to have Jupytext metadata in test notebooks:
         if fmt == 'ipynb' and 'jupytext' in notebook.metadata:  # pragma: no cover
             notebook.metadata.pop('jupytext')
-            jupytext.writef(nb_file, fmt)
+            jupytext.write(nb_file, fmt=fmt)
 
     create_mirror_file_if_missing(mirror_file, notebook, fmt)
 
     # Compare the text representation of the two notebooks
     if compare_notebook:
-        nb_mirror = jupytext.readf(mirror_file)
+        nb_mirror = jupytext.read(mirror_file)
         compare(nb_mirror, notebook)
         return
     elif ext == '.ipynb':
-        notebook = jupytext.readf(mirror_file)
+        notebook = jupytext.read(mirror_file)
         fmt.update({'extension': org_ext})
         with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', False):
             actual = jupytext.writes(notebook, fmt)
@@ -73,8 +73,8 @@ def assert_conversion_same_as_mirror(nb_file, fmt, mirror_name, compare_notebook
     # Compare the two notebooks
     if ext != '.ipynb':
         with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', False):
-            notebook = jupytext.readf(nb_file)
-            nb_mirror = jupytext.readf(mirror_file, fmt)
+            notebook = jupytext.read(nb_file)
+            nb_mirror = jupytext.read(mirror_file, fmt=fmt)
 
         if fmt.get('format_name') == 'sphinx':
             nb_mirror.cells = nb_mirror.cells[1:]
diff --git a/tests/test_pep8.py b/tests/test_pep8.py
index b4ca10f2..5f08ea96 100644
--- a/tests/test_pep8.py
+++ b/tests/test_pep8.py
@@ -1,6 +1,6 @@
 import pytest
 from testfixtures import compare
-from jupytext import readf, reads, writes
+from jupytext import read, reads, writes
 from jupytext.pep8 import next_instruction_is_function_or_class, cell_ends_with_function_or_class
 from jupytext.pep8 import cell_ends_with_code, cell_has_code, pep8_lines_between_cells
 from .utils import list_notebooks
@@ -157,7 +157,7 @@ def f(x):
                                      py_file.endswith('.py') and 'folding_markers' not in py_file])
 def test_no_metadata_when_py_is_pep8(py_file):
     """This test assumes that all Python files in the jupytext folder follow PEP8 rules"""
-    nb = readf(py_file)
+    nb = read(py_file)
 
     for i, cell in enumerate(nb.cells):
         if 'title' in cell.metadata:
diff --git a/tests/test_read_simple_ipynb.py b/tests/test_read_simple_ipynb.py
index 95d8a532..5b2ed257 100644
--- a/tests/test_read_simple_ipynb.py
+++ b/tests/test_read_simple_ipynb.py
@@ -9,7 +9,7 @@ def test_save_ipynb_with_jupytext_has_final_newline(tmpdir):
     file_jupytext = str(tmpdir.join('jupytext.ipynb'))
     file_nbformat = str(tmpdir.join('nbformat.ipynb'))
 
-    jupytext.writef(nb, file_jupytext)
+    jupytext.write(nb, file_jupytext)
     with open(file_nbformat, 'w') as fp:
         nbformat.write(nb, fp)
 
diff --git a/tests/test_save_multiple.py b/tests/test_save_multiple.py
index e9440788..7a856cb6 100644
--- a/tests/test_save_multiple.py
+++ b/tests/test_save_multiple.py
@@ -11,7 +11,7 @@ from .utils import list_notebooks
 
 @pytest.mark.parametrize('nb_file', list_notebooks(skip='66'))
 def test_rmd_is_ok(nb_file, tmpdir):
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     tmp_ipynb = 'notebook.ipynb'
     tmp_rmd = 'notebook.Rmd'
 
@@ -22,14 +22,14 @@ def test_rmd_is_ok(nb_file, tmpdir):
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
 
-    nb2 = jupytext.readf(str(tmpdir.join(tmp_rmd)))
+    nb2 = jupytext.read(str(tmpdir.join(tmp_rmd)))
 
     compare_notebooks(nb, nb2, 'Rmd')
 
 
 @pytest.mark.parametrize('nb_file', list_notebooks('Rmd'))
 def test_ipynb_is_ok(nb_file, tmpdir):
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     tmp_ipynb = 'notebook.ipynb'
     tmp_rmd = 'notebook.Rmd'
 
@@ -39,13 +39,13 @@ def test_ipynb_is_ok(nb_file, tmpdir):
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_rmd)
 
-    nb2 = jupytext.readf(str(tmpdir.join(tmp_ipynb)))
+    nb2 = jupytext.read(str(tmpdir.join(tmp_ipynb)))
     compare_notebooks(nb, nb2)
 
 
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py', skip='66'))
 def test_all_files_created(nb_file, tmpdir):
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
     tmp_ipynb = 'notebook.ipynb'
     tmp_rmd = 'notebook.Rmd'
     tmp_py = 'notebook.py'
@@ -56,10 +56,10 @@ def test_all_files_created(nb_file, tmpdir):
 
     cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
 
-    nb2 = jupytext.readf(str(tmpdir.join(tmp_py)))
+    nb2 = jupytext.read(str(tmpdir.join(tmp_py)))
     compare_notebooks(nb, nb2)
 
-    nb3 = jupytext.readf(str(tmpdir.join(tmp_rmd)))
+    nb3 = jupytext.read(str(tmpdir.join(tmp_rmd)))
     compare_notebooks(nb, nb3, 'Rmd')
 
 
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
index 41625e94..ef3036c1 100644
--- a/tests/test_unicode.py
+++ b/tests/test_unicode.py
@@ -11,7 +11,7 @@ except NameError:
 
 @pytest.mark.parametrize('nb_file', list_notebooks() + list_notebooks('Rmd'))
 def test_notebook_contents_is_unicode(nb_file):
-    nb = jupytext.readf(nb_file)
+    nb = jupytext.read(nb_file)
 
     for cell in nb.cells:
         assert cell.source == '' or isinstance(cell.source, unicode)
@@ -19,5 +19,5 @@ def test_notebook_contents_is_unicode(nb_file):
 
 def test_write_non_ascii(tmpdir):
     nb = jupytext.reads(u'Non-ascii contênt', 'Rmd')
-    jupytext.writef(nb, str(tmpdir.join('notebook.Rmd')))
-    jupytext.writef(nb, str(tmpdir.join('notebook.ipynb')))
+    jupytext.write(nb, str(tmpdir.join('notebook.Rmd')))
+    jupytext.write(nb, str(tmpdir.join('notebook.ipynb')))
diff --git a/tests/test_write_does_not_modify_notebook.py b/tests/test_write_does_not_modify_notebook.py
index 885e9bfc..6cafefdc 100644
--- a/tests/test_write_does_not_modify_notebook.py
+++ b/tests/test_write_does_not_modify_notebook.py
@@ -2,7 +2,7 @@ import pytest
 from copy import deepcopy
 from testfixtures import compare
 from itertools import product
-from jupytext import readf, writef, writes
+from jupytext import read, write, writes
 from jupytext.formats import long_form_one_format
 from .utils import list_notebooks
 
@@ -11,7 +11,7 @@ from .utils import list_notebooks
                          product(list_notebooks('ipynb_py') + list_notebooks('ipynb_R'),
                                  ['auto:light', 'auto:percent', 'md', '.Rmd', '.ipynb']))
 def test_write_notebook_does_not_change_it(nb_file, fmt, tmpdir):
-    nb_org = readf(nb_file)
+    nb_org = read(nb_file)
     nb_org_copied = deepcopy(nb_org)
     ext = long_form_one_format(fmt, nb_org.metadata)['extension']
 
@@ -19,5 +19,5 @@ def test_write_notebook_does_not_change_it(nb_file, fmt, tmpdir):
     compare(nb_org, nb_org_copied)
 
     tmp_dest = str(tmpdir.join('notebook' + ext))
-    writef(nb_org, tmp_dest, fmt)
+    write(nb_org, tmp_dest, fmt=fmt)
     compare(nb_org, nb_org_copied)