Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
python:pyinvoke [2017/11/28 10:41] marc dexet |
python:pyinvoke [2017/11/28 21:56] (Version actuelle) marc dexet |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
+ | |||
====== Invoke (pyinvoke) ====== | ====== Invoke (pyinvoke) ====== | ||
- | Invoke est un **ordonnanceur** et un **exécuteur** de tâches. | + | [[http://www.pyinvoke.org/|Invoke]] est un **ordonnanceur** et un **exécuteur** de tâches, c'est çà dire que il permet de décrire un workflow d'étapes pour atteindre un objectif et l'outil exécute celles-ci dans le bon ordre. |
Il repose sur la définition des tâches (//task//) via l'annotation //@task//. | Il repose sur la définition des tâches (//task//) via l'annotation //@task//. | ||
Ligne 197: | Ligne 199: | ||
- | ====== Aperçu de Invoke (pyinvoke) ====== | + | ===== Exemples ===== |
- | Invoke est un **ordonnanceur** et un **exécuteur** de tâches. | + | ==== Convertir en PDF des fichiers asciidoc ==== |
- | Il repose sur la définition des tâches (//task//) via l'annotation //@task//. | + | Imaginons un projet avec |
- | Ces tâches sont définies au sein d'un fichier //tasks.py//. | + | * un répertoire //main/doc// contenant la documentation en [[asciidoc]] |
+ | * un répertoire //build/doc// contenant la documentation finale | ||
+ | * un listing de fichiers dans //main/src// à mettre sous forme de document asciidoc | ||
- | //tasks.py// | + | Décomposons le workflow. |
- | <code python> | + | |
- | from invoke import task | + | |
- | @task | + | Il faut |
- | def remove_all(ctx): | + | |
- | ctx.run('rm -rf /tmp/my_dirs/work') | + | |
- | </code> | + | |
- | <code bash> | + | * générer un document //_listing.adoc// avec la liste des fichiers dans //main/src// |
- | $ invoke -l | + | * générer un PDF dans //build/doc// |
- | Available tasks: | + | * avant il faut s'assurer que //build/doc// existe |
- | + | ||
- | remove-all | + | |
- | + | ||
- | $ invoke remove-all | + | |
- | $ | + | |
- | </code> | + | |
- | + | ||
- | ===== Mise en oeuvre ===== | + | |
- | + | ||
- | Création de l'environnement virtuel de test | + | |
- | + | ||
- | <code bash> | + | |
- | $ ~/Workdirs$ virtualenv -p python3 test_invoke | + | |
- | Running virtualenv with interpreter /usr/bin/python3 | + | |
- | Using base prefix '/usr' | + | |
- | New python executable in /home/mdexet/Workdirs/test_invoke/bin/python3 | + | |
- | Also creating executable in /home/mdexet/Workdirs/test_invoke/bin/python | + | |
- | Installing setuptools, pip, wheel...done. | + | |
- | $ ~/Workdirs$ cd test_invoke/ | + | |
- | $ ~/Workdirs/test_invoke$ source ./bin/activate | + | |
- | </code> | + | |
- | + | ||
- | Installation de pyInvoke | + | |
- | + | ||
- | <code bash> | + | |
- | $ ~/Workdirs/test_invoke$ pip install invoke | + | |
- | Collecting invoke | + | |
- | Downloading invoke-0.21.0-py3-none-any.whl (153kB) | + | |
- | 100% |████████████████████████████████| 153kB 3.9MB/s | + | |
- | Installing collected packages: invoke | + | |
- | Successfully installed invoke-0.21.0 | + | |
- | + | ||
- | </code> | + | |
- | + | ||
- | ===== Créer une tâche ===== | + | |
- | + | ||
- | Pour créer une tâche il suffit | + | |
- | + | ||
- | * de créer un fichier //tasks.py// | + | |
- | * de créer une fonction qui prend //au moins// un //contexte// ctx en argument | + | |
- | * et de l'annoter avec //@task// | + | |
<code python> | <code python> | ||
- | @task | + | from invoke import task |
- | def do_something(ctx): | + | import os |
- | .... | + | import shutil |
- | + | from mako.template import Template | |
- | </code> | + | |
- | Une tâche peut exécuter une fonction python //ou// une commande système avec //ctx.run(<commande>)//. | + | work_dirname = '/tmp/project_doc' |
- | **Appel de fonction** | ||
- | <code python> | ||
- | def do_it_right(): | ||
- | //.... | ||
- | |||
- | @task | ||
- | def do_something(ctx): | ||
- | do_it_right() | ||
- | | ||
- | </code> | ||
- | **Appel de commande système** | + | def collectfilenames(path): |
- | <code python> | + | """ |
- | @task(pre=[clean_workdir, get_last_commit, compile_project], | + | Collect full qualified names of file within path. |
- | post=[deploy_artefact, update_website]) | + | :param path: directory to scan |
- | def build(ctx): | + | :return: full qualified filenames from this path |
- | ctx.run('maven install') | + | """ |
- | </code> | + | path_list = [] |
+ | for dirpath, _, files_name in os.walk(path): | ||
+ | for name in files_name: | ||
+ | path_list.append("{}/{}".format(dirpath, name)) | ||
+ | return path_list | ||
- | ===== Ordonnancement des tâches ===== | ||
- | Un tâche peut nécessiter plusieurs taches en amont et/ou en aval du processus. | + | def recreate_dir(dirname): |
+ | """ | ||
+ | Remove and recreate dirname | ||
+ | :param dirname: | ||
+ | """ | ||
+ | if os.path.exists(dirname): | ||
+ | shutil.rmtree(dirname) | ||
+ | os.makedirs(dirname) | ||
- | Pour modéliser cet enchaînement, il faut indiquer à la tache quels sont | ||
- | * ses prédécesseurs avec la propriété //pre// | ||
- | * et ses successeurs avec la propriété //post// | ||
- | |||
- | <code python> | ||
- | @task(pre=[clean_workdir, get_last_commit, compile_project], | ||
- | post=[deploy_artefact, update_website]) | ||
- | def build(ctx): | ||
- | do_something() | ||
- | </code> | ||
- | |||
- | La succession des tâches s'effectue de la première à la dernière **sauf** si | ||
- | |||
- | * une exception est levée | ||
- | * une commande système ( //ctx.run(<commande>)// ) retourne un code erreur. | ||
- | |||
- | **Exemple d'exception non gérée** | ||
- | <code python> | ||
@task | @task | ||
- | def get_last_commit(ctx): | + | def move_in_tempdir(ctx): |
- | a = 10/0 | + | recreate_dir(work_dirname) |
- | print('Get last commit') | + | with ctx.cd('main/doc'): |
- | </code> | + | ctx.run('cp * {}/'.format(work_dirname)) |
- | <code bash> | ||
- | $ invoke build | ||
- | Clean workdir | ||
- | Get last commit | ||
- | Traceback (most recent call last): | ||
- | File "/home/user/Workdirs/test_invoke/bin/invoke", line 11, in <module> | ||
- | sys.exit(program.run()) | ||
- | ... | ||
- | File "/home/user/Workdirs/test_invoke/tasks.py", line 10, in get_last_commit | ||
- | a = 10/0 | ||
- | ZeroDivisionError: division by zero | ||
- | </code> | ||
- | **Exemple d'exception gérée** | + | @task(pre=[move_in_tempdir]) |
- | <code python> | + | def create_listing(ctx): |
- | @task | + | with open('main/doc/_listing.adoc', 'w+') as listing: |
- | def get_last_commit(ctx): | + | listing.write(Template(filename='main/doc/listing.tpl').render(filenames=collectfilenames('main/src'))) |
- | raise Exit('hostname not found') | + | |
- | print('Get last commit') | + | |
- | </code> | + | |
- | <code bash> | ||
- | $ invoke build | ||
- | Clean workdir | ||
- | hostname not found | ||
- | </code> | ||
- | ==== Tâche par défaut ==== | + | @task(move_in_tempdir, create_listing) |
+ | def generate_pdf(ctx): | ||
+ | with ctx.cd(work_dirname): | ||
+ | ctx.run('asciidoctor-pdf doc.adoc') | ||
- | Pour désigner une tâche par défaut, il suffit de lui donner la propriété //default=True// | ||
- | <code python> | + | @task(pre=[generate_pdf], default=True) |
- | @task(default=True) | + | def copy_to_builddir(ctx): |
- | def build(ctx): | + | dirname = 'build/doc' |
- | ... | + | |
- | </code> | + | recreate_dir(dirname) |
+ | with ctx.cd(dirname): | ||
+ | ctx.run('cp {}/*.pdf ./'.format(work_dirname)) | ||
- | ==== Exemple complet d'ordonnancement ==== | ||
- | |||
- | <code python> | ||
- | from invoke import task | ||
- | |||
- | @task | ||
- | def clean_workdir(ctx): | ||
- | print('Clean workdir') | ||
- | |||
- | @task | ||
- | def get_last_commit(ctx): | ||
- | print('Get last commit') | ||
- | |||
- | @task | ||
- | def compile_project(ctx): | ||
- | print('Commit') | ||
- | |||
- | @task | ||
- | def deploy_artefact(ctx): | ||
- | print('Deploy') | ||
- | |||
- | @task | ||
- | def update_website(ctx): | ||
- | print('Update website') | ||
- | |||
- | @task( pre=[clean_workdir, get_last_commit, compile_project], | ||
- | post=[deploy_artefact, update_website]) | ||
- | def build(ctx): | ||
- | print('Building') | ||
</code> | </code> | ||
- | |||
- | <code bash> | ||
- | $ invoke build | ||
- | Clean workdir | ||
- | Get last commit | ||
- | Commit | ||
- | Building | ||
- | Deploy | ||
- | Update website | ||
- | </code> | ||
- | |||
===== FAQ ===== | ===== FAQ ===== | ||
- | ==== Comment récupérer le résultat d'une tâche ? ==== | ||
- | Si une tâche doit retourner un résultat pour la tâche suivante, comment le passer ? | ||
- | Apparemment il n'est pas possible de transmettre un résultat par l'intermédiaire du contexte. | ||
- | |||
- | Le seul moyen est de peut-être utiliser un dictionnaire de stockage global. | ||
- | |||
- | |||
- | <code python> | ||
- | |||
- | storage = {} | ||
- | |||
- | @task | ||
- | def do_something(ctx): | ||
- | storage['foo'] = 'bar' | ||
- | |||
- | |||
- | @task(pre=[do_something]) | ||
- | def use_something(ctx): | ||
- | bar = storage['foo'] | ||
- | </code> | ||
- | |||
- | ==== Comment répondre à une commande interactive ? ==== | ||
- | |||
- | //Exemple tiré de la documentation officielle// | ||
- | |||
- | Supposons que nous ayons une commande qui demande des informations de façon interactive : | ||
- | |||
- | <code> | ||
- | $ excitable-program | ||
- | When you give the OK, I'm going to do the things. All of them!! | ||
- | Are you ready? [Y/n] y | ||
- | OK! I just did all sorts of neat stuff. You're welcome! Bye! | ||
- | </code> | ||
- | |||
- | Pour cela il faut utiliser un //Responder// qui va surveiller la question et y répondre. | ||
- | |||
- | <code python> | ||
- | responder = Responder(pattern=r"Are you ready? \[Y/n\] ", response="y\n") | ||
- | ctx.run("excitable-program", watchers=[responder]) | ||
- | </code> | ||
==== Comment récupérer le résultat d'une tâche ? ==== | ==== Comment récupérer le résultat d'une tâche ? ==== | ||
Ligne 479: | Ligne 321: | ||
ctx.run("excitable-program", watchers=[responder]) | ctx.run("excitable-program", watchers=[responder]) | ||
</code> | </code> | ||
- | |||
- | |||
- | {{tag>python}} |