Objectifs

L'objectif est ici de créer un petit programme (my_toolkit) qui permettra d'appeler les différents sous programmes que nous avons créés:

  • head
  • cut
  • count
  • nb_exons
  • tx_len

Ce programme comportera des arguments qui pourront être positionnés via un appelle en ligne commande. Quelques exemples sont proposés ci-dessous afin de préciser notre objectif:

user@machine: python3 my_toolkit.py -p head -i ../data/hg38_5k.gtf -n 30 # lancer la commande head et visualiser 30 lignes.
user@machine: python3 my_toolkit.py -p cut -i ../data/hg38_5k.gtf  -c 3  # lancer la commande cut et visualiser la colonne  3.
user@machine: python3 my_toolkit.py -p nb_exons -i ../data/hg38_5k.gtf   # Renvoyer le nombre d'exons de chaque transcrit.
...

Entête du programme

Récupérez le codes des différents programmes que nous avons développés à ce stade (ces programmes sont tous codés sous la forme de fonctions comme vu dans le tutoriel précédant). Téléchargez l'archive final_program et décompressez la dans votre répertoire "home". Dans le sous dossier progs, créez un fichier my_tools.py. Ce fichier contiendra le programme principal qui appellera les autres fonctions développée. Le code suivant de ce tutoriel devra être écrit dans ce fichier.

Comme à l'habitude nous allons commencer notre programme (i) en déclarant l'utilisation éventuelle de caractères accentués et (ii) en déclarant dans une docstring l'objectif de ce programme:

# -*- coding: utf8 -*-
 
"""
    Main program with an argument parser that can be used to start the written subprograms (head, cut, count, tx_len,...).
"""

Importer les différents modules développé dans notre programme principal

Notre programme principal (my_toolkit.py) appellera les différents modules (i.e fichiers) que nous avons créé. Dans ces modules on pourra alors appelé les fonctions correspondantes (cf suite).

import head
import cut
import count
import nb_exons
import tx_len

L'objet argparse: création et analyse d'arguments en ligne de commande

Nous souhaiterions que notre programme soit capable d'analyser des arguments passé en ligne de commande. Cette opération est proposée par le package argparse de Python. Nous devons donc l'importer dans notre programme afin de pouvoir utiliser les outils proposés:

In [11]:
import argparse

Création d'un objet argparse

Afin de déclarer de nouveaux arguments qui seront proposés à l'utilisateur lorsqu'il souhaitera lancer le programme en ligne de commande, on doit tout d'abord créé un nouvel object de type (classe) ArgumentParser. Cet objet contient par ailleurs une description du programme qui apparaîtra lorsque l'utilisateur le demandera (argument -h).

In [12]:
# Dans le module argparse, on appelle la fonction ArgumentParser qui renvoie un object de type 
# ArgumentParser. On décrit, via l'argument 'description', l'objectif du programme. 
parser = argparse.ArgumentParser(description='A set of tools written during the informatic classes. These tools takes a gtf file as input and perform various tasks.')

# L'instruction suivante permet de demander 
# le type (la classe) de l'objet 
# et ne sera pas incluse dans le code final.
print(type(parser))
<class 'argparse.ArgumentParser'>

Déclaration des arguments

Afin de déclarer de nouveaux arguments accessibles par le programme, il suffit d'utiliser la méthode add_argument() sur l'objet de classe ArgumentParser (ici parser). Par exemple, étant donné que tous nos programmes vont devoir lire un fichier, nous allons créé un argument -i (pour inputfile). On pourrait cependant nommer différemment cet argument (e.g -f pour filename ou -g pour gtf...). Il s'agit ici d'un choix qui vise à donner à rendre le nom de l'argument explicite. Le code doit donc contenir:

parser.add_argument('-i',  # La version courte de l'argument.
                    '--inputfile',  # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    default=None,   # La valeur par défaut (aucune).
                    help="The input file name.") # L'aide pour cet argument.

De la même manière nous allons créé un argument (-p pour program) qui contrôlera quel sous programme doit être exécuté:

parser.add_argument('-p',           # La version courte de l'argument.
                    '--program',    # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    choices=["cut", "head", "count", "tx_len", "nb_exons"], # Les valeurs que l'argument peut prendre.
                    default=None,   # La valeur par défaut (aucune).
                    help="The subprogram to call.") # L'aide pour cet argument.

Exposer les arguments à l'utilisateur

A ce stade, lorsque le programme sera lancé, le parser d'arguments sera créé et contiendra la description des arguments. Afin que ces arguments soient exposé à l'utilisateur, nous devons utiliser la méthode parse_args().

args = parser.parse_args()
In [11]:
Maintenant, nous pouvons lancer le programme dans un terminal (attention pas dans ipython3).
user@machine$   python3 my_tools.py # il ne se passe rien
user@machine$   python3 my_tools.py -h # on invoque l'argument par défaut qui permet d'afficher les arguments
# les arguments du programme apparaissent.

usage: my_tools.py [-h] [-i INPUTFILE] [-p {cut,head,count,tx_len,nb_exons}]

A set of tools written during the informatic classes. These tools takes a gtf
file as input and perform various tasks.

optional arguments:
  -h, --help            show this help message and exit
  -i INPUTFILE, --inputfile INPUTFILE
                        The input file name.
  -p {cut,head,count,tx_len,nb_exons}, --program {cut,head,count,tx_len,nb_exons}
                        The subprogram to call.

Exercice: en faisant attention au type des valeurs attendues, ajoutez les deux arguments ci-dessous. Attention, la méthode parse_args() devra être appelé après leur déclaration. Relancez le programme en ligne de commande pour voir le résultat.

  • -n, --nbline : contrôlera le fonctionnement du sous-programme head.
  • -c, --column : contrôlera le fonctionnement du sous-programme cut.
# Le code à ce stade devient:
# -*- coding: utf8 -*-

"""
    Main program with an argument parser that can be used to start the written subprograms (head, cut, count, tx_len,...).
"""

import head
import cut
import count
import nb_exons
import tx_len




import argparse


parser = argparse.ArgumentParser(description='A set of tools written during the informatic classes. These tools takes a gtf file as input and perform various tasks.')

parser.add_argument('-i',  # La version courte de l'argument.
                    '--inputfile',  # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    default=None,   # La valeur par défaut (aucune).
                    help="The input file name.") # L'aide pour cet argument.

parser.add_argument('-p',           # La version courte de l'argument.
                    '--program',    # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    choices=["cut", "head", "count", "tx_len", "nb_exons"], # Les valeurs que l'argument peut prendre.
                    default=None,   # La valeur par défaut (aucune).
                    help="The subprogram to call.") # L'aide pour cet argument.


parser.add_argument('-n',
                    '--nbline',
                    type=int,
                    default=10,
                    help="If the 'head' command is used, the number of line to display")

parser.add_argument('-c',
                    '--column',
                    type=int,
                    default=1,
                    help="If the cut command is used, the column to print (one based) ")


args = parser.parse_args()

Ecrire une fonction main() dans laquelle on testera le contenu de l'objet args.

L'objet args contiendra une valeur (un attribut) pour inputfile, program, nbline et column. Ces valeurs seront fonction de la manière dont l'utilisateur a appelé le programme en ligne de commande.

Si l'utilisateur lance la ligne de commande suivante:

user@machine: python3 my_toolkit.py -p nb_exons -i ../data/hg38_5k.gtf   # Renvoyer le nombre d'exons de chaque transcrit.

Alors args.program (-p, --program) vaudra "nb_exons" et args.inputfile (-i, --inputfile) vaudra "../data/hg38_5k.gtf".

De même, si l'utilisateur entre la ligne de commande suivante:

user@machine: python3 my_toolkit.py -p tx_len -i ../data/hg38_5k.gtf   # la taille exon-intron compris des transcrits

Alors args.program (-p, --program) vaudra "tx_len" et args.inputfile (-i, --inputfile) vaudra "../data/hg38_5k.gtf".

On peut donc écrire une fonction main() qui testera le contenu de args.program. En fonction de ce contenu, elle appellera les différents sous-programmes.

In [3]:
def main():

    if args.program == "cut":
        
        cut.cut(args.inputfile, args.column) # on appelle la fonction cut présente dans le module (fichier) cut.py

    else:

        print("Please choose a program or use '-h' argument for more info.")

Exercice: complétez la fonction pour qu'elle prenne en charge les différentes valeurs que pourra prendre args.program. Utilisez une structure if..elif..else.

In [4]:
# Solution:
def main():

    if args.program == "cut":

        cut.cut(args.inputfile, args.column)

    elif args.program == "head":
    
        head.head(args.inputfile, args.nbline)

    elif args.program == "nb_exons":
    
        nb_exons.nb_exons(args.inputfile)

    elif args.program == "count":
    
        count.count(args.inputfile)
    
    else:
        print("Please choose a program or use '-h' argument for more info.")

Appeler la fonction main()

Comme nous l'avons vu précedemment, pour appeler la fonction main, il convient maintenant d'écrire:

if __name__ == "__main__": main()

Le code complet du programme principal

Le code complet de ce programme est donné ci-dessous. Vous pouvez maintenant tester ce programme en ligne de commande. N'oubliez pas de valider au préalable votre code avec pylint.

# -*- coding: utf8 -*-

"""
    Main program with an argument parser that can be used to start the written subprograms (head, cut, count, tx_len,...).
"""

import head
import cut
import count
import nb_exons
import tx_len




import argparse


parser = argparse.ArgumentParser(description='A set of tools written during the informatic classes. These tools takes a gtf file as input and perform various tasks.')

parser.add_argument('-i',  # La version courte de l'argument.
                    '--inputfile',  # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    default=None,   # La valeur par défaut (aucune).
                    help="The input file name.") # L'aide pour cet argument.

parser.add_argument('-p',           # La version courte de l'argument.
                    '--program',    # La version longue de l'argument.
                    type=str,       # Le type de l'argument.
                    choices=["cut", "head", "count", "tx_len", "nb_exons"], # Les valeurs que l'argument peut prendre.
                    default=None,   # La valeur par défaut (aucune).
                    help="The subprogram to call.") # L'aide pour cet argument.


parser.add_argument('-n',
                    '--nbline',
                    type=int,
                    default=10,
                    help="If the 'head' command is used, the number of line to display")

parser.add_argument('-c',
                    '--column',
                    type=int,
                    default=1,
                    help="If the cut command is used, the column to print (one based) ")


args = parser.parse_args()


def main():

    if args.program == "cut":

        cut.cut(args.inputfile, args.column)

    elif args.program == "head":
    
        head.head(args.inputfile, args.nbline)

    elif args.program == "nb_exons":
    
        nb_exons.nb_exons(args.inputfile)

    elif args.program == "count":
    
        count.count(args.inputfile)
    
    else:
        print("Please choose a program or use '-h' argument for more info.")


if __name__ == "__main__":

    main()

That's it...