Utilisation de notebooks IPython sous contrôle de version

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

le format de bloc-notes est tout à fait acceptable pour le contrôle de version: si l'on veut contrôler la version du bloc-notes et les sorties, alors cela fonctionne très bien. La gêne vient quand on veut seulement contrôler la version de l'entrée, en excluant les sorties de la cellule (aka. "construire des produits") qui peuvent être de grands blobs binaires, en particulier pour les films et les complots. En particulier, je essaie de trouver un bon flux de travail:

  • me permet de choisir entre inclure ou exclure la sortie,
  • m'empêche de propager accidentellement une sortie si Je ne la veux pas,
  • me permet de garder la sortie dans ma version locale,
  • me permet de voir quand j'ai des modifications dans les entrées en utilisant mon système de contrôle de version (i.e. si je contrôle seulement la version des entrées mais mon fichier local a des sorties, ensuite, j'aimerais pouvoir voir si les entrées ont changé (nécessitant une propagation). L'utilisation de la commande de statut de contrôle de version enregistrera toujours une différence puisque le fichier local a des sorties.)
  • me permet de mettre à jour mon carnet de travail (qui contient la sortie) à partir d'un carnet propre mis à jour. (mise à jour)

comme mentionné, si je choisis d'inclure les sorties (ce qui est souhaitable en utilisant nbviewer par exemple), alors tout va bien. Le problème est quand je ne pas veulent contrôler la version de sortie. Il y a des outils et des scripts pour enlever la sortie du carnet, mais souvent je rencontre les problèmes suivants:

  1. je commets accidentellement une version avec le résultat, polluant ainsi mon dépôt.
  2. j'autorise la sortie pour utiliser le contrôle de version, mais je préférerais vraiment garder la sortie dans ma copie locale (parfois, il faut un certain temps pour reproduire par exemple).
  3. certains des scripts qui suppriment la sortie changent légèrement le format par rapport à l'option de menu Cell/All Output/Clear , créant ainsi un bruit indésirable dans les diffs. Ce problème est résolu par certaines réponses.
  4. en tirant des modifications à une version propre du fichier, je dois trouver un moyen d'incorporer ces changements dans mon carnet de travail sans avoir à relire tout. (mise à jour)

j'ai envisagé plusieurs options que je vais examiner ci-dessous, mais je dois encore trouver une bonne solution globale. Une solution complète peut nécessiter des modifications à IPython, ou peut s'appuyer sur des scripts externes simples. J'utilise actuellement mercurial , mais je voudrais une solution qui fonctionne aussi avec git : une solution idéale serait Version-contrôle agnostique.

cette question a été discutée plusieurs fois, 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 très bien s'il nécessite une version récente (même de développement) de IPython ou une extension facile à installer.

mise à Jour: j'ai joué avec mon modifiée notebook "151960920 version permet en option de sauvegarder une version .clean avec chaque sauvegarde en utilisant les suggestions de Gregory Crosswhite . Cela satisfait la plupart de mes contraintes mais laisse les suivantes non résolues:

  1. ce n'est pas encore une solution standard (nécessite une modification de la source ipython. Est-il un moyen de parvenir à ce comportement avec une simple extension? Besoin d'une forme de sur-save crochet.
  2. Un problème que j'ai avec le courant le flux de travail entraîne des changements. Ceux-ci vont venir dans le fichier .clean , et doivent ensuite ê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 carnet, mais cela peut être une douleur, surtout si certains des résultats dépendent de longs calculs, calculs parallèles, etc.) Je n'ai pas une bonne idée sur la façon de résoudre ce encore. Peut-être un flux de travail impliquant une extension comme ipycache pourrait fonctionner, mais cela semble un peu trop compliqué.

Notes

suppression de la sortie (décapage)

  • lorsque le bloc-notes est en cours d'exécution, on peut utiliser l'option de menu Cell/All Output/Clear pour supprimer la sortie.
  • il y a des scripts pour supprimer la sortie, comme le script nbstripout.py qui suppriment la sortie, mais ne produisent pas la même sortie que l'utilisation de l'interface notebook. C'était finalement inclus dans le ipython/nbconvert repo, mais cela a été fermé indiquant que les changements sont maintenant inclus dans ipython/ipython , mais la fonctionnalité correspondante ne semble pas avoir été inclus encore. (mise à jour) cela dit, Gregory Crosswhite la solution montre que c'est assez facile à faire, même sans invoquer ipython/nbconvert , donc cette approche est probablement réalisable s'il peut être bien accroché. (L'attacher à chaque système de contrôle de version, cependant, ne semble pas être une bonne idée - cela devrait d'une certaine manière accrocher au mécanisme de carnet.)

groupes de discussion

  • Réflexions sur l'ordinateur portable de format pour le contrôle de version .

Questions

  • 977: carnet demandes de fonctionnalités (Ouvert) .
  • 1280: Claire-tous sur l'option de sauvegarde (Ouvert) . (Suit de cette discussion .)
  • 3295: autoexported cahiers: seule l'exportation est explicitement marqué les cellules (Fermé) . Résolu par extension 11 ajouter writeandexécute magic (fusionné) .

Pull Request

  • 1621: clair[] invite les numéros sur "Effacer Toutes les données de Sortie" (Fusionné) . (Voir aussi 2519 (fusionné) .)
  • 1563: clear_output améliorations (Fusionné) .
  • 3065: diff-capacité de carnets de notes (Fermé) .
  • 3291: ajouter l'option pour sauter les cellules de sortie lors de l'enregistrement. (Fermé) . Ce semble extrêmement pertinent, mais a été clôturé par la suggestion d'utiliser un filtre "propre/tache". Une question pertinente que pouvez-vous utiliser si vous voulez enlever la sortie avant d'exécuter git diff? ne semble pas avoir reçu de réponse.
  • 3312: WIP: blocs-notes, enregistrer des crochets (Fermé) .
  • 3747: ipynb -> ipynb transformer (fermé) . C'est rebasé dans 4175 .
  • 4175: nbconvert: Jinjaless exportateur de base (Fusionné) .
  • 142: utiliser STDIN dans nbstripout si aucune entrée n'est donnée (ouvert) .
489
demandé sur Vadim Kotov 2013-09-11 11:05:35

15 réponses

voici ma solution avec git. Il vous permet simplement d'ajouter et de commit (et de modifier)comme d'habitude: ces opérations ne modifieront pas votre arborescence de travail, et en même temps (re) lancer un carnet ne modifiera pas votre historique git.

bien que cela puisse probablement être adapté à d'autres VCSs, je sais qu'il ne répond pas à vos exigences (au moins l'agnosticité VSC). Pourtant, c'est parfait pour moi, et même si ce n'est pas particulièrement brillant, et beaucoup de gens probablement déjà utilisé, Je n'ai pas trouvé d'instructions claires sur la façon de le mettre en œuvre en googlant autour. Il 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. le Rendre 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
    

fait!

Limitations:

  • il ne fonctionne qu'avec git
  • en git, si vous êtes dans la branche somebranch et que vous faites git checkout otherbranch; git checkout somebranch , vous vous attendez généralement à ce que l'arbre de travail soit inchangé. Ici au contraire, vous aurez perdu la sortie et les cellules de la numérotation des cahiers dont la source diffère entre les deux branches.
  • plus en général, la sortie n'est pas versionnée du tout, comme avec la solution de Gregory. Afin de ne pas simplement le jeter à chaque fois que vous faites quelque chose impliquant une caisse, l'approche pourrait être modifiée en le stockant dans des fichiers séparés (mais notez qu'au moment où le code ci-dessus est lancé, l'id de propagation n'est pas connu!), et peut-être les versioner (mais notez que cela nécessiterait quelque chose de plus qu'un git commit notebook_file.ipynb , bien qu'il serait au moins garder git diff notebook_file.ipynb libre de déchets de base64).
  • cela dit, Soit dit en passant, si vous tirez le code (c.-à-d. commis par quelqu'un d'autre n'utilisant pas cette approche) qui contient une certaine sortie, la sortie est vérifiée normalement. Seule la production locale de la sortie est perdue.

ma solution reflète le fait que je n'aime pas personnellement garder produit des choses versioned-avis Que faire des fusions impliquant la sortie est presque garanti à invalider la sortie ou votre productivité ou les deux.

EDIT:

  • si vous adoptez la solution comme je l'ai suggéré - c'est - à-dire, globalement-vous aurez des problèmes au cas où pour certains git repo vous voulez à la sortie de la version. Donc, si vous voulez désactiver le filtrage de sortie pour un git spécifique référentiel, il suffit de créer un fichier .git/info / attributs , avec

    **.ipynb filter=

en tant que contenu. Clairement, de la même manière, il est possible de faire l'inverse: 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 donnent lieu à des impor teurs, essayez d'ajouter "ipython" avant le chemin du script:

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

EDIT : mai 2016 (mise à jour février 2017): il existe plusieurs alternatives à mon script - pour être complet, voici une liste de celles que je connais: nbstripout ( autres variantes ), nbstrip , jq .

105
répondu Pietro Battiston 2017-02-25 14:35:14

nous avons un projet collaboratif où le produit est les ordinateurs portables Jupyter, et nous avons utilisé une approche pour les six derniers mois qui fonctionne très bien: nous activons la sauvegarde des fichiers .py automatiquement et suivons à la fois les fichiers .ipynb et les fichiers .py .

de cette façon si quelqu'un veut voir/télécharger le dernier carnet, ils peuvent le faire via GitHub ou nbviewer, et si quelqu'un veut voir comment le code du carnet a changé, ils peuvent juste regardez les modifications apportées aux fichiers .py .

Pour Jupyter cahier des serveurs , ce qui 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 fichier jupyter_notebook_config.py et redémarrage du serveur de notebook.

si vous n'êtes pas sûr dans quel répertoire trouver votre fichier jupyter_notebook_config.py , vous pouvez taper jupyter --config-dir , et si vous ne trouvez pas le fichier là, vous pouvez le créer en tapant jupyter notebook --generate-config .

Pour Ipython 3 cahier des serveurs , ce qui 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 fichier ipython_notebook_config.py et redémarrage du serveur de notebook. Ces lignes sont tirées d'une réponse de GitHub issues @minrk a fourni et @dror les inclut également dans sa réponse SO.

Pour Ipython 2 cahier des serveurs , ce peut être accompli en démarrant le serveur en utilisant:

ipython notebook --script

ou en ajoutant la ligne

c.FileNotebookManager.save_script = True

au fichier ipython_notebook_config.py et redémarrage du serveur de notebook.

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

Voici notre projet sur github qui utilise cette approche : et voici un github exemple d'exploration des changements récents à un carnet .

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

55
répondu Rich Signell 2016-12-23 18:18:41

j'ai créé nbstripout , basé sur MinRKs gist , qui supporte à la fois Git et Mercurial (merci à mforbes). Il est conçu pour être utilisé seul sur la ligne de commande ou comme un filtre, qui est facilement (non)installé dans le dépôt courant via nbstripout install / nbstripout uninstall .

Obtenir à partir de PyPI ou tout simplement

pip install nbstripout
35
répondu kynan 2016-02-27 13:32:21

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

https://github.com/rossant/ipymd

13
répondu Spencer Boucher 2015-02-21 22:09:36

(2017-02)

stratégies

  • on_commit():
    • bande de la sortie > nom.ipynb ( nbstripout ,)
    • bande de la sortie > nom.propre.ipynb ( nbstripout ,)
    • toujours nbconvert python: name.ipynb.py ( nbconvert )
    • toujours convertir en markdown: nom.ipynb.md ( nbconvert , ipymd )
  • vcs.configurer():
    • git difftool, mergetool: nbdiff and nbmerge from nbdime

outils

11
répondu Wes Turner 2017-02-09 04:40:37

comme le souligne, le --script est déprécié dans 3.x . Cette approche peut être utilisée par l'application d'un post-enregistrement-crochet. En particulier, ajouter 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 est tiré de #8009 .

8
répondu Dror 2016-03-23 14:25:05

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 pourriez être en mesure de traduire mes commandes Git en leurs équivalents mercuriels.

pour l'arrière-plan, dans Git le commandement add stocke les changements qui ont été faits à un dossier dans une zone de transit. Une fois que vous avez fait cela, toute modification ultérieure du fichier sont ignorés par Git sauf si vous lui dites scène. Par conséquent, le script suivant, qui, pour chacun des fichiers donnés, élimine tout le outputs et prompt_number sections , met en scène le fichier dépouillé, puis restaure l'original:

NOTE: si vous obtenez un message d'erreur comme ImportError: No module named IPython.nbformat , alors 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 que le script a été exécuté sur les fichiers dont vous vouliez propager les modifications, exécutez simplement git commit .

6
répondu Gregory Crosswhite 2016-03-23 13:57:00

j'utilise une approche très pragmatique; ce qui fonctionne bien pour plusieurs cahiers, à plusieurs côtés. Et cela me permet même de "transférer" des carnets. Il fonctionne à la fois pour Windows comme Unix/MacOS.

Al pensait que c'est simple, c'est résoudre les problèmes ci-dessus...

Concept

Fondamentalement, ne pas piste .ipnyb -fichiers, seul le correspondant .py -fichiers.

En démarrant l'option notebook-server avec l'option --script , ce fichier est automatiquement créé/sauvegardé lorsque le notebook est sauvegardé.

ces .py - fichiers contiennent toutes les entrées; le non-code est enregistré dans les commentaires, comme le sont les bordures de cellules. Ces fichiers peuvent être lus/importés ( et traînés) dans le notebook-server pour (re)créer un notebook. Seule la sortie a disparu, jusqu'à ce qu'elle soit réémise.

personnellement j'utilise mercurial pour la version piste de la .py fichiers; et l'utilisation de la normale (ligne de commande) commandes pour ajouter, le check-in (ect). La plupart des autres (D)VCS le permettront.

Ses simples à suivre l'histoire, le .py sont de petite taille, textuelles et simple du diff. Une fois de temps en temps, nous avons besoin d'un clone (juste branche; démarrer un deuxième bloc-notes-couper là), ou une version plus ancienne (check-it out et importer dans un bloc-notes-serveur), etc.

conseils & astuces

  • ajouter *.ipynb ' .hgignore ", donc Mercurial sait qu'il peut ignorer ces fichiers
  • créer un script (bash) pour démarrer le serveur (avec l'option --script ) et faire la version-track it
  • enregistrer un carnet permet d'enregistrer le fichier .py , mais ne permet pas d'enregistrer le fichier .
    • C'est un drawback : on peut oublier que
    • c'est un fonctionnalité aussi: il est possible de sauvegarder un carnet (et de continuer plus tard) sans regrouper l'historique du dépôt.

Vœux

  • Il serait agréable d'avoir un des boutons pour le check-in/ajouter/etc dans le cahier de bord
  • Une caisse pour (par exemple) file@date+rev.py ) devrait être utile Il faudrait beaucoup de travail pour ajouter cela; et peut-être que je le ferai une fois. Jusqu'à présent, je le fais à la main.
5
répondu Albert 2014-07-22 13:35:30

pour suivre L'excellent script de Pietro Battiston, si vous obtenez une erreur de parsing 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
répondu Guillaume Dumas 2015-05-31 13:16:10

après avoir creusé autour, j'ai finalement trouvé ce crochet pré-save relativement simple sur le Jupyter docs . Il supprime les données de sortie de la cellule. Vous devez le coller dans le fichier jupyter_notebook_config.py (voir les instructions ci-dessous).

def scrub_output_pre_save(model, **kwargs):
    """scrub output before saving notebooks"""
    # only run on notebooks
    if model['type'] != 'notebook':
        return
    # only run on nbformat v4
    if model['content']['nbformat'] != 4:
        return

    for cell in model['content']['cells']:
        if cell['cell_type'] != 'code':
            continue
        cell['outputs'] = []
        cell['execution_count'] = None
        # Added by binaryfunt:
        if 'collapsed' in cell['metadata']:
            cell['metadata'].pop('collapsed', 0)

c.FileContentsManager.pre_save_hook = scrub_output_pre_save

à Partir de Riche Signell la réponse de :

si vous ne savez pas dans quel répertoire trouver votre fichier jupyter_notebook_config.py , vous pouvez taper jupyter --config-dir [dans l'invite de commande/terminal], et si vous ne trouvez pas le fichier, vous pouvez le créer en tapant jupyter notebook --generate-config .

3
répondu binaryfunt 2017-07-26 21:13:26

j'ai fait ce que Albert & Rich a - N'est pas la version .les fichiers ipynb (comme ceux-ci peuvent contenir des images, qui devient brouillon). À la place, soit vous lancez ipython notebook --script , soit vous mettez c.FileNotebookManager.save_script = True dans votre fichier de configuration, de sorte qu'un fichier .py est toujours créé lorsque vous sauvegardez votre ordinateur portable.

pour régénérer les notebooks (après avoir vérifié un repo ou changé une branche) j'ai mis le script py_file_to_notebooks.py dans le répertoire où je stocke mon portable.

maintenant, après avoir vérifié un rapport, Lancez python py_file_to_notebooks.py pour générer les fichiers ipynb. Après avoir changé de branche, vous pourriez avoir à exécuter python py_file_to_notebooks.py -ov pour écraser les fichiers ipynb existants.

juste pour être sûr, il est bon d'ajouter aussi *.ipynb à votre fichier .gitignore .

Edit: je n'ai plus le faire parce que (A) vous avez pour régénérer vos cahiers de py fichiers chaque fois que vous réglez une branche et (B) il y a d'autres trucs comme markdown dans les carnets que tu perds. À la place, j'efface la sortie des ordinateurs portables à l'aide d'un filtre git. La Discussion sur la façon de faire ceci est ici .

2
répondu Peter 2015-03-09 16:36:16

Ok, il semble donc que la meilleure solution actuelle , comme dans une discussion ici , est de faire un filtre git pour supprimer automatiquement la sortie des fichiers ipynb sur commit.

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 quand vous ne pouvez pas importer le dernier IPython: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output Et je l'ai ajouté à ma pension, disons dans ./relative/path/to/strip_notebook_output

a aussi ajouté le fichier .gitattributes fichier à la racine de l'opération, contenant:

*.ipynb filter=stripoutput

et a 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 source setup_git_filters.sh . La fantaisie $(git rev-parse...), c'est de trouver le chemin d'accès local votre repo sur N'importe quelle machine (Unix).

2
répondu Peter 2015-03-16 14:05:33

j'ai construit un paquet python qui résout ce problème""

https://github.com/brookisme/gitnb

il fournit un CLI avec une syntaxe inspirée de git pour suivre/mettre à jour/différencier les notebooks à l'intérieur de votre git repo.

Voici " un exemple

# add a notebook to be tracked
gitnb add SomeNotebook.ipynb

# check the changes before commiting
gitnb diff SomeNotebook.ipynb

# commit your changes (to your git repo)
gitnb commit -am "I fixed a bug"

notez que la dernière étape, où j'utilise "gitnb commit", c'est de vous engager dans votre git repo. Son essentiellement un emballage pour

# get the latest changes from your python notebooks
gitnb update

# commit your changes ** this time with the native git commit **
git commit -am "I fixed a bug"

il y a plusieurs autres méthodes, et peut être configuré de sorte qu'il nécessite plus ou moins l'entrée de l'utilisateur à chaque étape, mais c'est l'idée générale.

2
répondu brook 2017-06-02 15:21:20

Qu'en est-il de l'idée discutée dans le post ci-dessous, où la sortie du carnet devrait être conservée, avec l'argument que cela pourrait prendre beaucoup de temps pour le générer, et il est pratique depuis GitHub peut maintenant rendre des carnets. Il y a des crochets de sauvegarde automatique ajoutés pour l'exportation .fichier py, utilisé pour diffs et .html pour le partage avec les membres de l'équipe qui n'utilisent pas de bloc-notes ou de git.

https://towardsdatascience.com/version-control-for-jupyter-notebook-3e6cef13392d

0
répondu Krzysztof Słowiński 2017-12-10 19:55:28

cette extension jupyter permet aux utilisateurs de push jupyter notebooks directement sur github.

s'il vous Plaît regardez ici

https://github.com/sat28/githubcommit

0
répondu sat 2018-01-17 11:02:31