【问题标题】:How to use nbconvert as git textconv driver to enable effective version control of Jupyter Notebooks如何使用 nbconvert 作为 git textconv 驱动程序来启用 Jupyter Notebooks 的有效版本控制
【发布时间】:2018-12-22 18:14:58
【问题描述】:

我正在尝试做什么以及它与类似问题的不同之处

我想使用 Git 对 Jupyter Notebooks 进行版本控制。不幸的是,默认情况下,Git 和 Jupyter Notebooks 不能很好地运行。 .ipynb 文件是 .json 文件,不仅包含 Python 代码本身,还包含大量元数据(例如,单元格执行计数)和单元格输出。

大多数现有解决方案(例如,Using IPython notebooks under version control)依赖于从笔记本中删除输出和元数据。这 (i) 在 diff 时仍然保持 .json 文件结构,这很难阅读,并且 (ii) 意味着无法使用 Github 上的输出显示等功能,因为输出在提交之前就被删除了。

我的想法如下:每当我运行git diff 时,Git 会自动使用jupyter nbconvert --to python filename.ipynb 将我的*.ipynb 源文件转换为*.py 普通python 文件。然后,它应该只检测影响代码本身的更改(而不是执行计数和输出,因为它们已被 nbconvert 删除)而不实际删除它们,并且它应该使我的差异比未转换的 .ipynb 文件更具可读性。我不希望永久存储文件的.py 版本;它应该只用于git diff。我的理解是,这应该可以通过简单地将nbconvert 指定为[diff] textconv 驱动程序来实现,但我无法让它工作。

到目前为止我已经执行的步骤

我在/usr/local/bin 中创建了一个名为ipynb2py 的文件,其中包含

#!/bin/bash
jupyter nbconvert --to python $1

我已将以下内容添加到我的 .gitconfig 文件中

[diff "ipynb"]
    textconv = ipynb2py

以下内容添加到我的.gitattributes 文件中

*.ipynb diff=ipynb

ipynb textconv 驱动程序分配给.ipynb 格式的所有文件。

现在,我希望 git diff 每次运行它时都会自动执行转换(我知道这会大大减慢,但值得为 VCing 笔记本提供一个可行的选项),然后显示一个很好的可读差异,仅基于关于笔记本状态之间的区别转换后

当我执行git diff 时,它首先显示[NbConvertApp] Converting notebook,这告诉我Git 正在按预期触发转换。但是,在以fatal: unable to read files to diff 结尾的长时间 Python 回溯后,转换失败。

fatal 错误消息之前,我收到以下内容

nbformat.reader.NotJSONError: Notebook does not appear to be JSON: '\n# coding: utf-8\n\n# In[ ]:\n\nimport...

当然,我怀疑我的 ipynb2py 脚本调用 nbconvert 的方式存在问题,但在我的 repo 中运行 ipynb2py notebook.ipynb 效果很好,所以这不是原因。

什么可能导致此错误?除了返回文本文件之外,有效的textconv 驱动程序还有哪些要求?

完整的回溯

git diff
[NbConvertApp] Converting notebook /var/folders/9t/p55_4b9971j4wwp14_45wy900000gn/T//lR5q08_notebook.ipynb to python
Traceback (most recent call last):
File "/Users/user/anaconda/lib/python3.6/site-packages/nbformat/reader.py", line 14, in parse_json
nb_dict = json.loads(s, **kwargs)
File "/Users/user/anaconda/lib/python3.6/json/__init__.py", line 354, in loads
return _default_decoder.decode(s)
File "/Users/user/anaconda/lib/python3.6/json/decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/Users/user/anaconda/lib/python3.6/json/decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/Users/user/anaconda/bin/jupyter-nbconvert", line 11, in <module>
load_entry_point('nbconvert==5.1.1', 'console_scripts', 'jupyter-nbconvert')()
File "/Users/user/anaconda/lib/python3.6/site-packages/jupyter_core/application.py", line 266, in launch_instance
return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)
File "/Users/user/anaconda/lib/python3.6/site-packages/traitlets/config/application.py", line 658, in launch_instance
app.start()
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/nbconvertapp.py", line 305, in start
self.convert_notebooks()
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/nbconvertapp.py", line 473, in convert_notebooks
self.convert_single_notebook(notebook_filename)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/nbconvertapp.py", line 444, in convert_single_notebook
output, resources = self.export_single_notebook(notebook_filename, resources, input_buffer=input_buffer)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/nbconvertapp.py", line 373, in export_single_notebook
output, resources = self.exporter.from_filename(notebook_filename, resources=resources)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/exporters/exporter.py", line 171, in from_filename
return self.from_file(f, resources=resources, **kw)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbconvert/exporters/exporter.py", line 189, in from_file
return self.from_notebook_node(nbformat.read(file_stream, as_version=4), resources=resources, **kw)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbformat/__init__.py", line 141, in read
return reads(fp.read(), as_version, **kwargs)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbformat/__init__.py", line 74, in reads
nb = reader.reads(s, **kwargs)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbformat/reader.py", line 58, in reads
nb_dict = parse_json(s, **kwargs)
File "/Users/user/anaconda/lib/python3.6/site-packages/nbformat/reader.py", line 17, in parse_json
raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...")
nbformat.reader.NotJSONError: Notebook does not appear to be JSON: '\n# coding: utf-8\n\n# In[ ]:\n\nimport...
fatal: unable to read files to diff

【问题讨论】:

    标签: python git jupyter-notebook diff


    【解决方案1】:

    如果您仔细阅读documentation of gitattributes(其中描述了textconv 配置选项),您会注意到转换器程序必须将输出发送到标准输出:

    ...

    执行二进制文件的文本差异

    有时希望查看文本转换版本的差异 一些二进制文件。例如,一个文字处理器文档可以是 转换为 ASCII 文本表示,以及文本的差异 显示。即使这种转换丢失了一些信息, 产生的差异对于人类观察很有用(但不能应用 直接)。

    textconv 配置选项用于定义一个程序 进行这样的转换。该程序应采取单 参数,要转换的文件的名称,产生结果 标准输出上的文本。

    ...

    因此,您必须在转换命令中添加--stdout 选项:

    ipynb2py

    #!/bin/bash
    jupyter nbconvert --to python --stdout "$1"
    

    【讨论】:

      【解决方案2】:

      您是否尝试过直接提交笔记本。我在查看版本控制 jupyter 笔记本时看到了与您类似的帖子,但是当我尝试它时,它似乎工作正常。

      github 上的示例笔记本

      https://github.com/loegare/Test-Post-Please-Ignore/blob/master/Untitled%20Folder/Data%20Due%20Dilligence.ipynb

      【讨论】:

      • 它们在 Github 上渲染得非常好,但这从来都不是问题。尽管他们在 Github 上看起来不错,git diff 仍然将更改的执行计数视为对文件的更改,并将图表输出视为巨大的二进制块。当您尝试在具有多个协作者的 Git 工作流程中使用 Jupyter 笔记本时,这会变得非常痛苦。
      • 我想这很公平。我想我不是真的在那种环境中。感谢您为我澄清
      猜你喜欢
      • 2013-09-15
      • 1970-01-01
      • 2017-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多