Question Utilisation de carnets IPython sous contrôle de version


Quelle est une bonne stratégie pour garder IPython ordinateurs portables sous contrôle de version?

Le format de cahier est assez favorable au contrôle de version: si l'on veut contrôler la version du notebook et des sorties, cela fonctionne plutôt bien. L'ennui vient quand on veut seulement contrôler la version de l'entrée, à l'exclusion des sorties de cellules (aka "construire des produits") qui peuvent être de gros blobs binaires, en particulier pour les films et les intrigues. En particulier, j'essaie de trouver un bon flux de travail qui:

  • me permet de choisir entre inclure ou exclure la sortie,
  • m'empêche de commettre accidentellement une sortie si je ne le veux pas,
  • me permet de garder la sortie dans ma version locale,
  • me permet de voir quand j'ai des changements dans les entrées en utilisant mon système de contrôle de version (ie si la version seule contrôle les entrées mais mon fichier local a des sorties, alors je voudrais voir si les entrées ont changé ) L'utilisation de la commande status control de commande enregistre toujours une différence puisque le fichier local a des sorties.
  • me permet de mettre à jour mon cahier de travail (qui contient la sortie) à partir d'un cahier propre mis à jour. (mettre à jour)

Comme mentionné, si j'ai choisi d'inclure les sorties (ce qui est souhaitable lors de l'utilisation nbviewer par exemple), alors tout va bien. Le problème est quand je ne pas vouloir contrôler la version de la version. Il existe des outils et des scripts pour supprimer la sortie du bloc-notes, mais je rencontre fréquemment les problèmes suivants:

  1. Je commets accidentellement une version avec la sortie, polluant ainsi mon référentiel.
  2. J'efface la sortie pour utiliser le contrôle de version, mais je préfère vraiment conserver la sortie dans ma copie locale (parfois il faut un certain temps pour la reproduire par exemple).
  3. Certains des scripts qui sortent la sortie modifient légèrement le format par rapport à Cell/All Output/Clear option de menu, créant ainsi un bruit indésirable dans les diffs. Ceci est résolu par certaines des réponses.
  4. Lorsque vous apportez des modifications à une version propre du fichier, je dois trouver un moyen d'incorporer ces modifications dans mon carnet de travail sans avoir à tout réexécuter. (mettre à jour)

J'ai examiné plusieurs options que je discuterai ci-dessous, mais je n'ai pas encore trouvé de solution complète. Une solution complète peut nécessiter des modifications sur IPython ou peut s'appuyer sur des scripts externes simples. J'utilise actuellement mercuriel, mais voudrait une solution qui fonctionne aussi avec git: une solution idéale serait agnostique du contrôle de version.

Ce problème a été discuté à plusieurs reprises, mais il n'y a pas de solution définitive ou claire du point de vue de l'utilisateur. La réponse à cette question devrait fournir la stratégie définitive. C'est bien si cela nécessite une version récente (même développement) de IPython ou une extension facilement installée.

Mettre à jour: J'ai joué avec mon cahier modifiéversion qui enregistre en option un .clean version avec chaque sauvegarde en utilisant Les suggestions de Gregory Crosswhite. Cela satisfait la plupart de mes contraintes mais laisse les questions suivantes non résolues:

  1. Ce n'est pas encore une solution standard (nécessite une modification de la source ipython.) Existe-t-il un moyen d'obtenir ce comportement avec une simple extension?
  2. Un problème que j'ai avec le flux de travail actuel est d'effectuer des modifications. Ceux-ci viendront à la .clean fichier, puis doivent être intégrés d'une manière ou d'une autre dans ma version de travail. (Bien sûr, je peux toujours ré-exécuter le cahier, mais cela peut être pénible, surtout si certains des résultats dépendent de calculs longs, de calculs parallèles, etc.) Je n'ai pas encore une bonne idée de la façon de le résoudre . Peut-être un workflow impliquant une extension comme ipycache pourrait fonctionner, mais cela semble un peu trop compliqué.

Remarques

Retrait (extraction) Sortie

  • Lorsque le portable fonctionne, on peut utiliser le Cell/All Output/Clear option de menu pour supprimer la sortie.
  • Il existe des scripts pour supprimer la sortie, tels que le script nbstripout.py qui supprime la sortie, mais ne produit pas la même sortie que l'utilisation de l'interface portable. Cela a finalement été inclus dans le ipython / nbconvert repo, mais cela a été fermé en déclarant que les changements sont maintenant inclus dans ipython / ipython, mais la fonctionnalité correspondante ne semble pas encore avoir été incluse. (mettre à jour) Cela étant dit, La solution de Gregory Crosswhite montre que c'est assez facile à faire, même sans invoquer ipython / nbconvert, donc cette approche est probablement réalisable si elle peut être connectée correctement. (L'attacher à chaque système de contrôle de version, cependant, ne semble pas être une bonne idée - cela devrait en quelque sorte être relié au mécanisme de l'ordinateur portable.)

Groupes de discussion

Problèmes

Tirer des demandes


468
2017-09-11 07:05


origine


Réponses:


Voici ma solution avec git. Il vous permet de simplement ajouter et valider (et diff) comme d'habitude: ces opérations ne modifieront pas votre arbre de travail, et en même temps (re) l'exécution d'un cahier ne modifiera pas votre histoire git.

Bien que cela puisse probablement être adapté à d'autres VCS, je sais qu'il ne répond pas à vos exigences (au moins l'agnosticité VSC). Pourtant, il est parfait pour moi, et bien que ce ne soit pas particulièrement brillant, et beaucoup de gens l'utilisent probablement déjà, je n'ai pas trouvé d'instructions claires sur la façon de l'implémenter en faisant des recherches. Cela peut donc être utile à d'autres personnes.

  1. Enregistrer un fichier avec ce contenu quelque part (pour ce qui suit, supposons ~/bin/ipynb_output_filter.py)
  2. Rendez-le exécutable (chmod +x ~/bin/ipynb_output_filter.py)
  3. Créer le fichier ~/.gitattributes, avec le contenu suivant

    *.ipynb    filter=dropoutput_ipynb
    
  4. Exécutez les commandes suivantes:

    git config --global core.attributesfile ~/.gitattributes
    git config --global filter.dropoutput_ipynb.clean ~/bin/ipynb_output_filter.py
    git config --global filter.dropoutput_ipynb.smudge cat
    

Terminé!

Limites:

  • ça marche seulement avec git
  • en git, si vous êtes dans la branche somebranch et vous faites git checkout otherbranch; git checkout somebranch, vous attendez généralement que l'arbre de travail soit inchangé. Ici, vous aurez perdu la sortie et la numérotation des cellules des cahiers dont la source diffère entre les deux branches.
  • plus généralement, la sortie n'est pas du tout versionnée, comme avec la solution de Gregory. Afin de ne pas le jeter à chaque fois que vous faites quelque chose impliquant une caisse, l'approche pourrait être changée en le stockant dans des fichiers séparés (mais notez qu'au moment où le code ci-dessus est exécuté, l'identifiant de validation n'est pas connu!), et éventuellement leur version (mais notez que cela nécessiterait quelque chose de plus qu'un git commit notebook_file.ipynb, bien que cela garderait au moins git diff notebook_file.ipynb exempt de déchets de base64).
  • cela dit, d'ailleurs si vous tirez du code (c'est-à-dire commis par quelqu'un d'autre n'utilisant pas cette approche) qui contient une certaine sortie, la sortie est extraite normalement. Seule la production produite localement est perdue.

Ma solution reflète le fait que je n'aime pas personnellement garder les choses générées en version - notez que faire des fusions impliquant la sortie est presque garanti pour invalider la sortie ou votre productivité ou tous les deux.

MODIFIER:

  • si vous adoptez la solution comme je l'ai suggéré - c'est-à-dire, globalement - vous aurez des problèmes en cas de vouloir à la sortie de la version. Donc si vous voulez désactiver le filtrage de sortie pour un référentiel git spécifique, créez simplement à l'intérieur un fichier .git / info / attributs, avec

    **. ipynb filter =

comme contenu. Clairement, de la même manière, il est possible de faire le contraire: activer le filtrage seulement pour un référentiel spécifique.

  • le code est maintenant maintenu dans son propre git repo

  • Si les instructions ci-dessus aboutissent à ImportErrors, essayez d'ajouter "ipython" avant le chemin du script:

    git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py
    

MODIFIER: Mai 2016 (mis à jour en février 2017): il existe plusieurs alternatives à mon script - pour être complet, voici une liste de ceux que je connais: nbstripout (autre  variantes), nbstrip, jq.


99
2017-12-30 17:35



Nous avons un projet collaboratif où le produit est Jupyter Notebooks, et nous utilisons une approche qui fonctionne très bien depuis six mois: nous activons la sauvegarde de .py fichiers automatiquement et suivre les deux .ipynb les fichiers et le .py des dossiers.

De cette façon, si quelqu'un veut visualiser / télécharger le dernier bloc-notes, il peut le faire via github ou nbviewer, et si quelqu'un veut voir comment le code du bloc-notes a changé, il peut simplement regarder les modifications apportées au .py des dossiers.

Pour Jupyter serveurs portables, cela peut être accompli en ajoutant les lignes

import os
from subprocess import check_call

def post_save(model, os_path, contents_manager):
    """post-save hook for converting notebooks to .py scripts"""
    if model['type'] != 'notebook':
        return # only do this for notebooks
    d, fname = os.path.split(os_path)
    check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d)

c.FileContentsManager.post_save_hook = post_save

au jupyter_notebook_config.py fichier et redémarrage du serveur portable.

Si vous ne savez pas dans quel répertoire trouver votre jupyter_notebook_config.py fichier, vous pouvez taper jupyter --config-diret si vous ne trouvez pas le fichier, vous pouvez le créer en tapant jupyter notebook --generate-config.

Pour Ipython 3 serveurs portables, cela peut être accompli en ajoutant les lignes

import os
from subprocess import check_call

def post_save(model, os_path, contents_manager):
    """post-save hook for converting notebooks to .py scripts"""
    if model['type'] != 'notebook':
        return # only do this for notebooks
    d, fname = os.path.split(os_path)
    check_call(['ipython', 'nbconvert', '--to', 'script', fname], cwd=d)

c.FileContentsManager.post_save_hook = post_save

au ipython_notebook_config.py fichier et redémarrage du serveur portable. Ces lignes proviennent d'une réponse aux problèmes de github @minrk fourni et @dror les inclut également dans sa réponse SO.

Pour Ipython 2 serveurs portables, cela peut être accompli en démarrant le serveur en utilisant:

ipython notebook --script

ou en ajoutant la ligne

c.FileNotebookManager.save_script = True

au ipython_notebook_config.py fichier et redémarrage du serveur portable.

Si vous ne savez pas dans quel répertoire trouver votre ipython_notebook_config.py fichier, vous pouvez taper ipython locate profile defaultet si vous ne trouvez pas le fichier, vous pouvez le créer en tapant ipython profile create.

Voici notre projet sur github qui utilise cette approche: et voici un exemple github d'explorer les changements récents sur un cahier.

Nous avons été très heureux avec cela.


54
2017-09-10 12:13



J'ai créé nbstripout, basé sur MinRKs, qui supporte à la fois Git et Mercurial (grâce à mforbes). Il est destiné à être utilisé seul sur la ligne de commande ou en tant que filtre, qui est facilement (dés) installé dans le référentiel actuel via nbstripout install / nbstripout uninstall.

Obtenez-le de PyPI ou simplement

pip install nbstripout

33
2018-02-27 13:32



Voici une nouvelle solution de Cyrille Rossant pour IPython 3.0, qui persiste à supprimer les fichiers plutôt que les fichiers ipymd basés sur json:

https://github.com/rossant/ipymd


13
2018-02-21 22:09



(2017-02)

stratégies

  • on_commit ():
    • enlevez la sortie> name.ipynb (nbstripout,)
    • dépouiller la sortie> name.clean.ipynb (nbstripout,)
    • toujours nbconvert en python: name.ipynb.py (nbconvert)
    • toujours convertir en markdown: name.ipynb.md (nbconvert, ipymd)
  • vcs.configure ():
    • git difftool, mergetool: nbdiff et nbmerge de nbdime

outils


10
2018-02-09 04:40



Comme souligné par le --script est déprécié dans 3.x. Cette approche peut être utilisée en appliquant un post-save-hook. En particulier, ajoutez ce qui suit à ipython_notebook_config.py:

import os
from subprocess import check_call

def post_save(model, os_path, contents_manager):
    """post-save hook for converting notebooks to .py scripts"""
    if model['type'] != 'notebook':
        return # only do this for notebooks
    d, fname = os.path.split(os_path)
    check_call(['ipython', 'nbconvert', '--to', 'script', fname], cwd=d)

c.FileContentsManager.post_save_hook = post_save

Le code provient de # 8009.


8
2018-03-11 15:27



Malheureusement, je ne sais pas grand-chose sur Mercurial, mais je peux vous donner une solution possible qui fonctionne avec Git, dans l'espoir que vous puissiez traduire mes commandes Git en leurs équivalents Mercurial.

Pour le fond, dans Git le add La commande stocke les modifications apportées à un fichier dans une zone de transfert. Une fois que vous avez fait cela, toutes les modifications ultérieures du fichier sont ignorées par Git à moins que vous ne lui disiez de les mettre en scène également. Par conséquent, le script suivant, qui, pour chacun des fichiers donnés, supprime tous les outputs et prompt_number sections, met en scène le fichier supprimé, puis restaure l'original:

REMARQUE: Si cela fonctionne, vous obtenez un message d'erreur comme ImportError: No module named IPython.nbformat, puis utilisez ipython pour exécuter le script au lieu de python.

from IPython.nbformat import current
import io
from os import remove, rename
from shutil import copyfile
from subprocess import Popen
from sys import argv

for filename in argv[1:]:
    # Backup the current file
    backup_filename = filename + ".backup"
    copyfile(filename,backup_filename)

    try:
        # Read in the notebook
        with io.open(filename,'r',encoding='utf-8') as f:
            notebook = current.reads(f.read(),format="ipynb")

        # Strip out all of the output and prompt_number sections
        for worksheet in notebook["worksheets"]:
            for cell in worksheet["cells"]:
               cell.outputs = []
               if "prompt_number" in cell:
                    del cell["prompt_number"]

        # Write the stripped file
        with io.open(filename, 'w', encoding='utf-8') as f:
            current.write(notebook,f,format='ipynb')

        # Run git add to stage the non-output changes
        print("git add",filename)
        Popen(["git","add",filename]).wait()

    finally:
        # Restore the original file;  remove is needed in case
        # we are running in windows.
        remove(filename)
        rename(backup_filename,filename)

Une fois le script exécuté sur les fichiers dont vous souhaitez valider les modifications, exécutez-le git commit.


6
2017-11-04 04:27



J'utilise une approche très pragmatique; qui fonctionnent bien pour plusieurs cahiers, de plusieurs côtés. Et cela me permet même de «transférer» les cahiers. Il fonctionne à la fois pour Windows comme Unix / MacOS.
Al pense que c'est simple, est résoudre les problèmes ci-dessus ...

Concept

Fondamentalement, faire ne pas suivre la .ipnyb-files, seul le correspondant .py-des dossiers.
En commençant le ordinateur portable avec le --script En option, ce fichier est automatiquement créé / enregistré lors de la sauvegarde du notebook.

Ceux .pyles fichiers contiennent toutes les entrées; le non-code est sauvegardé dans les commentaires, tout comme les limites de la cellule. Ces fichiers peuvent être lus / importés (et traînés) dans le cahier-serveur pour (re) créer un cahier. Seule la sortie est partie; jusqu'à ce qu'il soit réexécuté.

Personnellement j'utilise mercuriel pour suivre la version .py des dossiers; et utilisez les commandes normales (ligne de commande) pour ajouter, archiver (ect) pour cela. La plupart des autres (D) VCS le permettront.

C'est simple de suivre l'histoire maintenant; la .py sont petites, textuelles et simples à différencier. Une fois et un moment, nous avons besoin d'un clone (branchez-le, lancez un second notebook-sever), ou d'une version plus ancienne (check-it et importez dans un notebook-server), etc.

Conseils & Astuces

  • Ajouter * .ipynb à '.hgignore', Mercurial sait qu’il peut ignorer ces fichiers
  • Créez un script (bash) pour démarrer le serveur (avec le --script option) et faire la version-suivre
  • Enregistrer un ordinateur portable permet d'économiser .py-file, mais fait ne pas vérifiez-le.
    • C'est un inconvénient: On peut oublier que
    • C'est un fonctionnalité aussi: Il est possible de sauvegarder un cahier (et continuer plus tard) sans regrouper l'historique du dépôt.

Vœux

  • Ce serait bien d'avoir un bouton pour check-in / add / etc dans le tableau de bord du notebook
  • Une caisse à (par exemple) file@date+rev.py) devrait être utile Ce serait beaucoup de travail pour ajouter cela; et peut-être que je le ferai une fois. Jusqu'à présent, je le fais juste à la main.

4
2017-07-22 13:35



Pour suivre l'excellent script de Pietro Battiston, si vous obtenez une erreur d'analyse Unicode comme ceci:

Traceback (most recent call last):
  File "/Users/kwisatz/bin/ipynb_output_filter.py", line 33, in <module>
write(json_in, sys.stdout, NO_CONVERT)
  File "/Users/kwisatz/anaconda/lib/python2.7/site-packages/IPython/nbformat/__init__.py", line 161, in write
fp.write(s)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2014' in position 11549: ordinal not in range(128)

Vous pouvez ajouter au début du script:

reload(sys)
sys.setdefaultencoding('utf8')

3
2018-05-31 12:26



J'ai fait ce qu'Albert et Rich ont fait - Ne pas version fichiers .ipynb (car ils peuvent contenir des images, ce qui devient salissant). Au lieu de cela, soit toujours exécuter ipython notebook --script ou mettre c.FileNotebookManager.save_script = True dans votre fichier de configuration, de sorte qu'un (versionnable) .py Le fichier est toujours créé lorsque vous enregistrez votre carnet.

Pour régénérer des cahiers (après avoir vérifié un repo ou changé de branche) j'ai mis le script py_file_to_notebooks.py dans le répertoire où je stocke mes cahiers.

Maintenant, après avoir vérifié un dépôt, il suffit de courir python py_file_to_notebooks.py pour générer les fichiers ipynb. Après avoir changé de branche, vous devrez peut-être exécuter python py_file_to_notebooks.py -ov pour écraser les fichiers ipynb existants.

Juste pour être du bon côté, il est bon d'ajouter aussi *.ipynb à ton .gitignore fichier.

Edit: Je ne fais plus cela parce que (A) vous devez régénérer vos cahiers à partir de fichiers py chaque fois que vous passez en revue une branche et (B) il y a d'autres choses comme la démarque dans les cahiers que vous perdez. À la place, je supprime la sortie des ordinateurs portables à l'aide d'un filtre git. Une discussion sur la façon de procéder est ici.


2
2018-02-18 14:38



Ok, donc ça ressemble à la meilleure solution actuelle, selon une discussion ici, est de faire un filtre git pour enlever automatiquement la sortie des fichiers ipynb lors de la validation.

Voici ce que j'ai fait pour le faire fonctionner (copié de cette discussion):

J'ai légèrement modifié le fichier nbstripout de cfriedline pour donner une erreur informative lorsque vous ne pouvez pas importer le dernier IPython: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output Et ajouté à mon repo, disons dans ./relative/path/to/strip_notebook_output

Ajout du fichier .gitattributes à la racine du repo, contenant:

*.ipynb filter=stripoutput

Et créé un setup_git_filters.sh contenant

git config filter.stripoutput.clean "$(git rev-parse --show-toplevel)/relative/path/to/strip_notebook_output" 
git config filter.stripoutput.smudge cat
git config filter.stripoutput.required true

Et a couru source setup_git_filters.sh. Le truc fantaisie $ (git rev-parse ...) est de trouver le chemin local de votre repo sur n'importe quelle machine (Unix).


2
2018-03-16 14:05