From 025ee4c1e8fe52e132244e5e955bd39b6912ef68 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 12:10:24 +0200 Subject: [PATCH 1/9] Read README as unicode for #71 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7a43e88e8..65611075c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages this_directory = path.abspath(path.dirname(__file__)) -with open(path.join(this_directory, 'README.md')) as f: +with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: long_description = f.read() setup( From 4446795b68b1d561fe17763c8ad00ce6c4aa90e2 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 12:29:01 +0200 Subject: [PATCH 2/9] io.open has 'encoding' argument for Python 2.7 #71 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 65611075c..de56fc0c8 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from os import path +from io import open from setuptools import setup, find_packages this_directory = path.abspath(path.dirname(__file__)) From 5079626256f034291a3e197426989a9d18473f17 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 14:08:29 +0200 Subject: [PATCH 3/9] Target for v0.6.4 --- HISTORY.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 883324e06..56f05cfa6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,6 +6,19 @@ Release History dev +++ +0.6.4 (2018-09-??) ++++++++++++++++++++ + +**Improvements** + +- PENDING: Jupytext will not load paired notebook when text representation is out of date (#63) +- Package tested against Python 3.7 (#68) + +**BugFixes** + +- PENDING: Allow unicode characters in notebook path (#70) +- Read README.md as unicode in `setup.py`, fix pypi.org display and `pip install` issues (#71) + 0.6.3 (2018-09-07) +++++++++++++++++++ From e86167071fac1ad0ea8960dcb526826e5689266c Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 22:14:00 +0200 Subject: [PATCH 4/9] pylint --- jupytext/file_format_version.py | 2 +- tests/test_cli.py | 2 +- tests/test_load_multiple.py | 2 +- tests/test_unicode.py | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/jupytext/file_format_version.py b/jupytext/file_format_version.py index b70e4b50d..1c95c7403 100644 --- a/jupytext/file_format_version.py +++ b/jupytext/file_format_version.py @@ -58,7 +58,7 @@ def check_file_version(notebook, source_path, outputs_path): return # Version larger than minimum readable version - if version <= current and version >= min_file_format_version(ext): + if min_file_format_version(ext) <= version <= current: return # Not merging? OK diff --git a/tests/test_cli.py b/tests/test_cli.py index 22b042e1e..f3334bffa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -175,5 +175,5 @@ def test_combine_lower_version_raises(tmpdir): {'.py': '1.0'}): with mock.patch( 'jupytext.file_format_version.MIN_FILE_FORMAT_VERSION', - {'.py': '1.0'}): + {'.py': '1.0'}): jupytext(args=[tmp_nbpy, '--to', 'ipynb', '--update']) diff --git a/tests/test_load_multiple.py b/tests/test_load_multiple.py index 380842b69..3f04982f9 100644 --- a/tests/test_load_multiple.py +++ b/tests/test_load_multiple.py @@ -62,5 +62,5 @@ def test_combine_lower_version_raises(tmpdir): {'.py': '1.0'}): with mock.patch( 'jupytext.file_format_version.MIN_FILE_FORMAT_VERSION', - {'.py': '1.0'}): + {'.py': '1.0'}): cm.get(tmp_ipynb) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index b34950a00..838649c42 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -1,13 +1,12 @@ # coding: utf-8 -import sys import pytest import jupytext from .utils import list_all_notebooks try: - unicode # Python 2 + unicode # Python 2 except NameError: - unicode = str # Python 3 + unicode = str # Python 3 @pytest.mark.parametrize('nb_file', list_all_notebooks('.ipynb') + From b4c12343fe0038afc14b53834422b79e6b2d019e Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 22:14:59 +0200 Subject: [PATCH 5/9] Test and fix unicode file names #70 --- jupytext/contentsmanager.py | 23 +++++++++++------- requirements.txt | 1 - setup.py | 2 +- tests/test_contentsmanager.py | 44 +++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py index 8e36ef90a..ec2cfb1c5 100644 --- a/jupytext/contentsmanager.py +++ b/jupytext/contentsmanager.py @@ -3,21 +3,26 @@ import os import nbformat import mock -import six +from tornado.web import HTTPError +from traitlets import Unicode +from traitlets.config import Configurable + +from notebook.services.contents.filemanager import FileContentsManager try: import notebook.transutils # noqa except ImportError: pass -from notebook.services.contents.filemanager import FileContentsManager -from tornado.web import HTTPError -from traitlets import Unicode -from traitlets.config import Configurable -import jupytext +import jupytext from . import combine from .file_format_version import check_file_version +try: + unicode # Python 2 +except NameError: + unicode = str # Python 3 + def _jupytext_writes(ext): def _writes(nbk, version=nbformat.NO_CONVERT, **kwargs): @@ -58,7 +63,7 @@ def check_formats(formats): return check_formats([formats]) validated_group = [] for fmt in group: - if not isinstance(fmt, six.string_types): + if not isinstance(fmt, unicode): raise ValueError('Extensions should be strings among {}' ', not {}.\n{}' .format(str(jupytext.NOTEBOOK_EXTENSIONS), @@ -173,14 +178,14 @@ def _read_notebook(self, os_path, as_version=4, break if source_format != fmt: - self.log.info('Reading SOURCE from {}' + self.log.info(u'Reading SOURCE from {}' .format(os.path.basename(file + source_format))) nb_outputs = nbk nbk = self._read_notebook(file + source_format, as_version=as_version, load_alternative_format=False) elif outputs_format != fmt: - self.log.info('Reading OUTPUTS from {}' + self.log.info(u'Reading OUTPUTS from {}' .format(os.path.basename(file + outputs_format))) if outputs_format != fmt: nb_outputs = self._read_notebook(file + outputs_format, diff --git a/requirements.txt b/requirements.txt index 44fd03cb6..800aacc61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ nbformat>=4.0.0 pyyaml mock -six testfixtures diff --git a/setup.py b/setup.py index de56fc0c8..1dc31b8ae 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ 'pynotebook = jupytext:PyNotebookExporter', 'rnotebook = jupytext:RNotebookExporter']}, tests_require=['pytest'], - install_requires=['nbformat>=4.0.0', 'mock', 'pyyaml', 'six', 'testfixtures'], + install_requires=['nbformat>=4.0.0', 'mock', 'pyyaml', 'testfixtures'], license='MIT', classifiers=('Development Status :: 4 - Beta', 'Environment :: Console', diff --git a/tests/test_contentsmanager.py b/tests/test_contentsmanager.py index a27d83d8e..3d5c7639a 100644 --- a/tests/test_contentsmanager.py +++ b/tests/test_contentsmanager.py @@ -1,3 +1,5 @@ +# coding: utf-8 + import os import sys import pytest @@ -119,3 +121,45 @@ def test_load_save_rename_nbpy_default_config(nb_file, tmpdir): assert not os.path.isfile(str(tmpdir.join('new.ipynb'))) assert os.path.isfile(str(tmpdir.join('new.nb.py'))) + + +@pytest.mark.skipif(isinstance(TextFileContentsManager, str), + reason=TextFileContentsManager) +@pytest.mark.parametrize('nb_file', list_py_notebooks('.ipynb')) +def test_load_save_rename_non_ascii_path(nb_file, tmpdir): + tmp_ipynb = u'notebôk.ipynb' + tmp_nbpy = u'notebôk.nb.py' + + cm = TextFileContentsManager() + cm.default_jupytext_formats = 'ipynb' + tmpdir = u'' + str(tmpdir) + cm.root_dir = tmpdir + + # open ipynb, save nb.py, reopen + nb = readf(nb_file) + cm.save(model=dict(type='notebook', content=nb), path=tmp_nbpy) + nbpy = cm.get(tmp_nbpy) + compare_notebooks(nb, nbpy['content']) + + # open ipynb + nbipynb = cm.get(tmp_ipynb) + compare_notebooks(nb, nbipynb['content']) + + # save ipynb + cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb) + + # rename nbpy + cm.rename(tmp_nbpy, u'nêw.nb.py') + assert not os.path.isfile(os.path.join(tmpdir, tmp_ipynb)) + assert not os.path.isfile(os.path.join(tmpdir, tmp_nbpy)) + + assert os.path.isfile(os.path.join(tmpdir, u'nêw.ipynb')) + assert os.path.isfile(os.path.join(tmpdir, u'nêw.nb.py')) + + # rename ipynb + cm.rename(u'nêw.ipynb', tmp_ipynb) + assert os.path.isfile(os.path.join(tmpdir, tmp_ipynb)) + assert not os.path.isfile(os.path.join(tmpdir, tmp_nbpy)) + + assert not os.path.isfile(os.path.join(tmpdir, u'nêw.ipynb')) + assert os.path.isfile(os.path.join(tmpdir, u'nêw.nb.py')) From 172103a065a2042c42865db5dad1895fc8be93f5 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Tue, 11 Sep 2018 22:28:48 +0200 Subject: [PATCH 6/9] Make 'fmt' unicode always #70 --- jupytext/contentsmanager.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py index ec2cfb1c5..05780e245 100644 --- a/jupytext/contentsmanager.py +++ b/jupytext/contentsmanager.py @@ -18,11 +18,6 @@ from . import combine from .file_format_version import check_file_version -try: - unicode # Python 2 -except NameError: - unicode = str # Python 3 - def _jupytext_writes(ext): def _writes(nbk, version=nbformat.NO_CONVERT, **kwargs): @@ -63,7 +58,9 @@ def check_formats(formats): return check_formats([formats]) validated_group = [] for fmt in group: - if not isinstance(fmt, unicode): + try: + fmt = u'' + fmt + except UnicodeDecodeError: raise ValueError('Extensions should be strings among {}' ', not {}.\n{}' .format(str(jupytext.NOTEBOOK_EXTENSIONS), From 74f1d396f3cbd6336519065b8289449f086bedbc Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Wed, 12 Sep 2018 00:39:46 +0200 Subject: [PATCH 7/9] Test text notebook modification time #63 --- jupytext/combine.py | 3 +- jupytext/contentsmanager.py | 187 ++++++++++++++++++++++------------ jupytext/jupytext.py | 6 -- tests/test_contentsmanager.py | 44 ++++++++ 4 files changed, 168 insertions(+), 72 deletions(-) diff --git a/jupytext/combine.py b/jupytext/combine.py index 9a7a87267..eeed5c2ff 100644 --- a/jupytext/combine.py +++ b/jupytext/combine.py @@ -25,6 +25,7 @@ def combine_inputs_with_outputs(nb_source, nb_outputs): metadata = ocell.metadata cell.metadata.update({k: metadata[k] for k in metadata - if metadata in _IGNORE_METADATA}) + if k not in _IGNORE_METADATA + + ['trusted']}) remaining_output_cells = remaining_output_cells[(i + 1):] break diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py index 05780e245..5422f66a8 100644 --- a/jupytext/contentsmanager.py +++ b/jupytext/contentsmanager.py @@ -1,10 +1,11 @@ """ContentsManager that allows to open Rmd, py, R and ipynb files as notebooks """ import os +from datetime import timedelta import nbformat import mock from tornado.web import HTTPError -from traitlets import Unicode +from traitlets import Unicode, Float from traitlets.config import Configurable from notebook.services.contents.filemanager import FileContentsManager @@ -56,6 +57,9 @@ def check_formats(formats): # In early versions (0.4 and below), formats could be a list of # extensions. We understand this as a single group return check_formats([formats]) + + # Return ipynb on first position (save that file first, for #63) + has_ipynb = False validated_group = [] for fmt in group: try: @@ -77,7 +81,13 @@ def check_formats(formats): .format(str(group), fmt, str(jupytext.NOTEBOOK_EXTENSIONS), expected_format)) - validated_group.append(fmt) + if fmt == '.ipynb': + has_ipynb = True + else: + validated_group.append(fmt) + + if has_ipynb: + validated_group = ['.ipynb'] + validated_group if validated_group: validated_formats.append(validated_group) @@ -118,8 +128,21 @@ def all_nb_extensions(self): 'comma separated', config=True) + outdated_text_notebook_margin = Float( + 1.0, + help='Refuse to overwrite inputs of a ipynb notebooks with those of a' + 'text notebook when the text notebook plus margin is older than' + 'the ipynb notebook', + config=True) + def format_group(self, fmt, nbk=None): """Return the group of extensions that contains 'fmt'""" + # Backward compatibility with nbrmd + for key in ['nbrmd_formats', 'nbrmd_format_version']: + if nbk and key in nbk.metadata: + nbk.metadata[key.replace('nbrmd', 'jupytext')] = \ + nbk.metadata.pop(key) + jupytext_formats = ((nbk.metadata.get('jupytext_formats') if nbk else None) or self.default_jupytext_formats) @@ -134,78 +157,23 @@ def format_group(self, fmt, nbk=None): if fmt in group: return group + # No such group, but 'ipynb'? Return current fmt + 'ipynb' if ['.ipynb'] in jupytext_formats: - return [fmt, '.ipynb'] + return ['.ipynb', fmt] return [fmt] - def _read_notebook(self, os_path, as_version=4, - load_alternative_format=True): + def _read_notebook(self, os_path, as_version=4): """Read a notebook from an os path.""" - file, fmt, ext = file_fmt_ext(os_path) + _, ext = os.path.splitext(os_path) if ext in self.nb_extensions: with mock.patch('nbformat.reads', _jupytext_reads(ext)): - nbk = super(TextFileContentsManager, self) \ + return super(TextFileContentsManager, self) \ ._read_notebook(os_path, as_version) else: - nbk = super(TextFileContentsManager, self) \ + return super(TextFileContentsManager, self) \ ._read_notebook(os_path, as_version) - if not load_alternative_format: - return nbk - - fmt_group = self.format_group(fmt, nbk) - - source_format = fmt - outputs_format = fmt - - # Source format is first non ipynb format found on disk - if fmt.endswith('.ipynb'): - for alt_fmt in fmt_group: - if not alt_fmt.endswith('.ipynb') and \ - os.path.isfile(file + alt_fmt): - source_format = alt_fmt - break - # Outputs taken from ipynb if in group, if file exists - else: - for alt_fmt in fmt_group: - if alt_fmt.endswith('.ipynb') and \ - os.path.isfile(file + alt_fmt): - outputs_format = alt_fmt - break - - if source_format != fmt: - self.log.info(u'Reading SOURCE from {}' - .format(os.path.basename(file + source_format))) - nb_outputs = nbk - nbk = self._read_notebook(file + source_format, - as_version=as_version, - load_alternative_format=False) - elif outputs_format != fmt: - self.log.info(u'Reading OUTPUTS from {}' - .format(os.path.basename(file + outputs_format))) - if outputs_format != fmt: - nb_outputs = self._read_notebook(file + outputs_format, - as_version=as_version, - load_alternative_format=False) - else: - nb_outputs = None - - try: - check_file_version(nbk, file + source_format, - file + outputs_format) - except ValueError as err: - raise HTTPError(400, str(err)) - - if nb_outputs: - combine.combine_inputs_with_outputs(nbk, nb_outputs) - if self.notary.check_signature(nb_outputs): - self.notary.sign(nbk) - elif not fmt.endswith('.ipynb'): - self.notary.sign(nbk) - - return nbk - def _save_notebook(self, os_path, nb): """Save a notebook to an os_path.""" os_file, fmt, _ = file_fmt_ext(os_path) @@ -221,7 +189,8 @@ def _save_notebook(self, os_path, nb): super(TextFileContentsManager, self) \ ._save_notebook(os_path_fmt, nb) - def get(self, path, content=True, type=None, format=None): + def get(self, path, content=True, type=None, format=None, + load_alternative_format=True): """ Takes a path for an entity and returns its model""" path = path.strip('/') @@ -230,7 +199,95 @@ def get(self, path, content=True, type=None, format=None): (type is None and any([path.endswith(ext) for ext in self.all_nb_extensions()]))): - return self._notebook_model(path, content=content) + model = self._notebook_model(path, content=content) + if not content: + return model + + if not load_alternative_format: + return model + + nb_file, fmt, _ = file_fmt_ext(path) + fmt_group = self.format_group(fmt, model['content']) + + source_format = fmt + outputs_format = fmt + + # Source format is first non ipynb format found on disk + if fmt.endswith('.ipynb'): + for alt_fmt in fmt_group: + if not alt_fmt.endswith('.ipynb') and \ + self.exists(nb_file + alt_fmt): + source_format = alt_fmt + break + # Outputs taken from ipynb if in group, if file exists + else: + for alt_fmt in fmt_group: + if alt_fmt.endswith('.ipynb') and \ + self.exists(nb_file + alt_fmt): + outputs_format = alt_fmt + break + + if source_format != fmt: + self.log.info(u'Reading SOURCE from {}'.format( + os.path.basename(nb_file + source_format))) + model_outputs = model + model = self.get(nb_file + source_format, content=content, + type=type, format=format, + load_alternative_format=False) + elif outputs_format != fmt: + self.log.info(u'Reading OUTPUTS from {}'.format( + os.path.basename(nb_file + outputs_format))) + model_outputs = self.get(nb_file + outputs_format, + content=content, + type=type, format=format, + load_alternative_format=False) + else: + model_outputs = None + + try: + check_file_version(model['content'], + nb_file + source_format, + nb_file + outputs_format) + except ValueError as err: + raise HTTPError(400, str(err)) + + # Make sure we're not overwriting ipynb cells with an outdated + # text file + try: + if model_outputs and model_outputs['last_modified'] > \ + model['last_modified'] + \ + timedelta(seconds=self.outdated_text_notebook_margin): + raise HTTPError( + 400, + u'\n' + '{out} (last modified {out_last})\n' + 'seems more recent than ' + '{src} (last modified {src_last})\n' + 'Please either:\n' + '- open {src} in a text editor, make sure it is ' + 'up to date, and save it,\n' + '- or delete {src} if not up to date,\n' + '- or increase check margin by adding, say, \n' + ' c.ContentsManager.' + 'outdated_text_notebook_margin = 5 ' + '# in seconds # or float("inf")\n' + 'to your .jupyter/jupyter_notebook_config.py ' + 'file\n'.format(src=nb_file + source_format, + src_last=model['last_modified'], + out=nb_file + outputs_format, + out_last=model_outputs[ + 'last_modified'])) + except OverflowError: + pass + + if model_outputs: + combine.combine_inputs_with_outputs(model['content'], + model_outputs['content']) + elif not fmt.endswith('.ipynb'): + self.notary.sign(model['content']) + self.mark_trusted_cells(model['content'], path) + + return model return super(TextFileContentsManager, self) \ .get(path, content, type, format) diff --git a/jupytext/jupytext.py b/jupytext/jupytext.py index 5a770a42f..6e7d3bc3e 100644 --- a/jupytext/jupytext.py +++ b/jupytext/jupytext.py @@ -77,12 +77,6 @@ def to_notebook(self, text): set_main_and_cell_language(metadata, cells, self.ext) - # Backward compatibility with nbrmd - for key in ['nbrmd_formats', 'nbrmd_format_version']: - if key in metadata: - metadata[key.replace('nbrmd', 'jupytext')] = \ - metadata.pop(key) - return new_notebook(cells=cells, metadata=metadata) diff --git a/tests/test_contentsmanager.py b/tests/test_contentsmanager.py index 3d5c7639a..fea9b6d61 100644 --- a/tests/test_contentsmanager.py +++ b/tests/test_contentsmanager.py @@ -2,7 +2,9 @@ import os import sys +import time import pytest +from tornado.web import HTTPError import jupytext from jupytext import TextFileContentsManager, readf from jupytext.compare import compare_notebooks @@ -163,3 +165,45 @@ def test_load_save_rename_non_ascii_path(nb_file, tmpdir): assert not os.path.isfile(os.path.join(tmpdir, u'nêw.ipynb')) assert os.path.isfile(os.path.join(tmpdir, u'nêw.nb.py')) + + +@pytest.mark.skipif(isinstance(TextFileContentsManager, str), + reason=TextFileContentsManager) +@pytest.mark.parametrize('nb_file', list_py_notebooks('.ipynb')[:1]) +def test_outdated_text_notebook(nb_file, tmpdir): + # 1. write py ipynb + tmp_ipynb = u'notebook.ipynb' + tmp_nbpy = u'notebook.py' + + cm = TextFileContentsManager() + cm.default_jupytext_formats = 'py,ipynb' + cm.outdated_text_notebook_margin = 0 + cm.root_dir = str(tmpdir) + + # open ipynb, save py, reopen + nb = readf(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) + + # 2. check that time of ipynb <= py + assert model_ipynb['last_modified'] <= model_py['last_modified'] + + # 3. wait some time + time.sleep(0.5) + + # 4. touch ipynb + with open(str(tmpdir.join(tmp_ipynb)), 'a'): + os.utime(str(tmpdir.join(tmp_ipynb)), None) + + # 5. test error + with pytest.raises(HTTPError): + cm.get(tmp_nbpy) + + # 6. test OK with + cm.outdated_text_notebook_margin = 1.0 + cm.get(tmp_nbpy) + + # 7. test OK with + cm.outdated_text_notebook_margin = float("inf") + cm.get(tmp_nbpy) From 2bf62fa63360d10377478fbde6b85e5d5afcfae3 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Wed, 12 Sep 2018 01:08:48 +0200 Subject: [PATCH 8/9] Remove notebook signature before testing notebook trust --- jupytext/combine.py | 4 ++-- jupytext/contentsmanager.py | 6 ++---- tests/test_trust_notebook.py | 26 +++++++++++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/jupytext/combine.py b/jupytext/combine.py index eeed5c2ff..244cbf703 100644 --- a/jupytext/combine.py +++ b/jupytext/combine.py @@ -25,7 +25,7 @@ def combine_inputs_with_outputs(nb_source, nb_outputs): metadata = ocell.metadata cell.metadata.update({k: metadata[k] for k in metadata - if k not in _IGNORE_METADATA + - ['trusted']}) + if k == 'trusted' or + k not in _IGNORE_METADATA}) remaining_output_cells = remaining_output_cells[(i + 1):] break diff --git a/jupytext/contentsmanager.py b/jupytext/contentsmanager.py index 5422f66a8..10fc2daf0 100644 --- a/jupytext/contentsmanager.py +++ b/jupytext/contentsmanager.py @@ -193,12 +193,11 @@ def get(self, path, content=True, type=None, format=None, load_alternative_format=True): """ Takes a path for an entity and returns its model""" path = path.strip('/') + nb_file, fmt, ext = file_fmt_ext(path) if self.exists(path) and \ (type == 'notebook' or - (type is None and - any([path.endswith(ext) - for ext in self.all_nb_extensions()]))): + (type is None and ext in self.all_nb_extensions())): model = self._notebook_model(path, content=content) if not content: return model @@ -206,7 +205,6 @@ def get(self, path, content=True, type=None, format=None, if not load_alternative_format: return model - nb_file, fmt, _ = file_fmt_ext(path) fmt_group = self.format_group(fmt, model['content']) source_format = fmt diff --git a/tests/test_trust_notebook.py b/tests/test_trust_notebook.py index aad41276d..a6fb348f5 100644 --- a/tests/test_trust_notebook.py +++ b/tests/test_trust_notebook.py @@ -39,15 +39,27 @@ def test_ipynb_notebooks_can_be_trusted(nb_file, tmpdir): cm.default_jupytext_formats = 'ipynb,py' cm.root_dir = str(tmpdir) - nb = cm.get(file) + model = cm.get(file) + + cm.save(model, py_file) + + # Unsign and test notebook + nb = model['content'] + for cell in nb.cells: + if 'trusted' in cell.metadata: + cell.metadata.pop('trusted') + + cm.notary.unsign(nb) - # Sign notebook explicitely (save it, and reload without - # validating to remove 'trusted' metadata in cells) - cm.save(nb, py_file) + model = cm.get(file) + for cell in model['content'].cells: + assert 'trusted' not in cell.metadata or not cell.metadata['trusted'] + + # Trust and reload cm.trust_notebook(py_file) - nb = cm.get(file) - for cell in nb['content'].cells: + model = cm.get(file) + for cell in model['content'].cells: assert cell.metadata.get('trusted', True) # Remove py file, content should be the same @@ -56,4 +68,4 @@ def test_ipynb_notebooks_can_be_trusted(nb_file, tmpdir): for cell in nb2['content'].cells: assert cell.metadata.get('trusted', True) - assert nb['content'] == nb2['content'] + assert model['content'] == nb2['content'] From cc8193081e145c2d9da2ed3ec06760fa97ef8c83 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Wed, 12 Sep 2018 01:14:28 +0200 Subject: [PATCH 9/9] v0.6.4 --- HISTORY.rst | 11 ++++------- binder/requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 56f05cfa6..4b06ee4a3 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,21 +3,18 @@ Release History --------------- -dev -+++ - -0.6.4 (2018-09-??) +0.6.4 (2018-09-12) +++++++++++++++++++ **Improvements** -- PENDING: Jupytext will not load paired notebook when text representation is out of date (#63) +- Jupytext will not load paired notebook when text representation is out of date (#63) - Package tested against Python 3.7 (#68) **BugFixes** -- PENDING: Allow unicode characters in notebook path (#70) -- Read README.md as unicode in `setup.py`, fix pypi.org display and `pip install` issues (#71) +- Allow unicode characters in notebook path (#70) +- Read README.md as unicode in `setup.py` (#71) 0.6.3 (2018-09-07) +++++++++++++++++++ diff --git a/binder/requirements.txt b/binder/requirements.txt index e67c4f726..289fc88fc 100644 --- a/binder/requirements.txt +++ b/binder/requirements.txt @@ -1,4 +1,4 @@ -jupytext>=0.6.3 +jupytext>=0.6.4 plotly matplotlib pandas diff --git a/setup.py b/setup.py index 1dc31b8ae..8b12e1e52 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='jupytext', - version='0.6.3', + version='0.6.4', author='Marc Wouts', author_email='marc.wouts@gmail.com', description='Jupyter notebooks as Markdown documents, '