[OK] CHEATSHEET COMPLET - ARGPARSE (ARGUMENTS EN LIGNE DE COMMANDE) PYTHON


[OK] 1. CONCEPT DE BASE
[OK] argparse permet de créer des interfaces en ligne de commande professionnelles
# Sans argparse (basique)
import sys
if len(sys.argv) > 1:
    filename = sys.argv[1]

# Avec argparse (professionnel)
import argparse

parser = argparse.ArgumentParser(description='Mon programme')
parser.add_argument('filename', help='Fichier à traiter')
args = parser.parse_args()
print(args.filename)

# Avantages d'argparse:
# - Parsing automatique des arguments
# - Génération automatique du --help
# - Validation des types
# - Arguments optionnels et obligatoires
# - Valeurs par défaut
# - Messages d'erreur clairs


[OK] 2. CRÉATION D'UN PARSER BASIQUE
[OK] ArgumentParser est le point d'entrée

import argparse

# Créer le parser
parser = argparse.ArgumentParser(
    prog='mon_script',              # Nom du programme (par défaut: sys.argv[0])
    description='Description du programme',  # Affiché dans --help
    epilog='Texte affiché à la fin du --help'  # Texte de fin
)

# Parser les arguments
args = parser.parse_args()

# Exemple complet minimal
parser = argparse.ArgumentParser(description='Traite des fichiers')
args = parser.parse_args()

# Usage: python script.py --help


[OK] 3. ARGUMENTS POSITIONNELS (OBLIGATOIRES)
[OK] Arguments sans -- ou -, obligatoires dans l'ordre

parser = argparse.ArgumentParser()

# Argument positionnel simple
parser.add_argument('filename', help='Nom du fichier')

# Usage: python script.py data.txt
args = parser.parse_args()
print(args.filename)  # 'data.txt'


# Plusieurs arguments positionnels
parser = argparse.ArgumentParser()
parser.add_argument('input', help='Fichier source')
parser.add_argument('output', help='Fichier destination')

# Usage: python script.py input.txt output.txt
args = parser.parse_args()
print(args.input, args.output)


# Avec type
parser = argparse.ArgumentParser()
parser.add_argument('count', type=int, help='Nombre de répétitions')

# Usage: python script.py 5
args = parser.parse_args()
print(args.count)  # 5 (entier, pas string)


[OK] 4. ARGUMENTS OPTIONNELS (FLAGS)
[OK] Arguments avec -- ou -, optionnels

parser = argparse.ArgumentParser()

# Option courte (-v)
parser.add_argument('-v', help='Mode verbose')

# Option longue (--verbose)
parser.add_argument('--verbose', help='Mode verbose')

# Combinaison courte et longue (recommandé)
parser.add_argument('-v', '--verbose', help='Mode verbose')

# Usage:
# python script.py -v
# python script.py --verbose
args = parser.parse_args()
print(args.verbose)


# Option avec valeur
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', help='Fichier de sortie')

# Usage: python script.py -o result.txt
# ou:     python script.py --output result.txt
args = parser.parse_args()
print(args.output)


[OK] 5. TYPES D'ARGUMENTS

import argparse

parser = argparse.ArgumentParser()

# 1. String (par défaut)
parser.add_argument('name', help='Votre nom')

# 2. Integer
parser.add_argument('-n', '--number', type=int, help='Un nombre')

# 3. Float
parser.add_argument('-p', '--price', type=float, help='Un prix')

# 4. File (ouvre automatiquement le fichier)
parser.add_argument('-f', '--file', type=argparse.FileType('r'), 
                    help='Fichier à lire')

# 5. Boolean (flag)
parser.add_argument('-v', '--verbose', action='store_true',
                    help='Active le mode verbose')

# 6. Type personnalisé
def positive_int(value):
    """Valide que la valeur est un entier positif"""
    ivalue = int(value)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError(f"{value} n'est pas un entier positif")
    return ivalue

parser.add_argument('-c', '--count', type=positive_int, 
                    help='Nombre positif')

# Usage:
args = parser.parse_args(['Alice', '-n', '42', '-p', '19.99', '-v'])
print(args.name)     # 'Alice'
print(args.number)   # 42 (int)
print(args.price)    # 19.99 (float)
print(args.verbose)  # True


[OK] 6. ACTIONS DES ARGUMENTS

parser = argparse.ArgumentParser()

# 1. store (par défaut) - Stocke la valeur
parser.add_argument('-n', '--name', action='store')

# 2. store_true - Stocke True si présent, False sinon
parser.add_argument('-v', '--verbose', action='store_true')

# 3. store_false - Stocke False si présent, True sinon
parser.add_argument('-q', '--quiet', action='store_false')

# 4. store_const - Stocke une constante
parser.add_argument('--debug', action='store_const', const=True)

# 5. append - Ajoute à une liste (peut être répété)
parser.add_argument('-i', '--include', action='append',
                    help='Répertoire à inclure (peut être répété)')
# Usage: python script.py -i dir1 -i dir2 -i dir3
# args.include sera ['dir1', 'dir2', 'dir3']

# 6. append_const - Ajoute une constante à une liste
parser.add_argument('--verbose', dest='verbosity', action='append_const', const=1)
parser.add_argument('--quiet', dest='verbosity', action='append_const', const=-1)

# 7. count - Compte le nombre de fois que l'option apparaît
parser.add_argument('-v', '--verbose', action='count', default=0,
                    help='Augmente la verbosité (répétable: -vvv)')
# Usage: python script.py -vvv
# args.verbose sera 3

# 8. help - Affiche l'aide et quitte (automatique avec --help)

# 9. version - Affiche la version et quitte
parser.add_argument('--version', action='version', version='%(prog)s 1.0.0')


[OK] 7. VALEURS PAR DÉFAUT

parser = argparse.ArgumentParser()

# Défaut simple
parser.add_argument('-n', '--name', default='Anonymous',
                    help='Votre nom (défaut: Anonymous)')

# Défaut avec type
parser.add_argument('-c', '--count', type=int, default=10,
                    help='Nombre de répétitions (défaut: 10)')

# Défaut None (argument optionnel non fourni)
parser.add_argument('-o', '--output', default=None,
                    help='Fichier de sortie')

# Usage:
args = parser.parse_args([])  # Aucun argument
print(args.name)   # 'Anonymous'
print(args.count)  # 10
print(args.output) # None

# Avec valeurs fournies
args = parser.parse_args(['-n', 'Alice', '-c', '5'])
print(args.name)   # 'Alice'
print(args.count)  # 5


[OK] 8. ARGUMENTS REQUIS

parser = argparse.ArgumentParser()

# Argument optionnel mais requis
parser.add_argument('-u', '--username', required=True,
                    help='Nom d\'utilisateur (requis)')

parser.add_argument('-p', '--password', required=True,
                    help='Mot de passe (requis)')

# Optionnel non requis
parser.add_argument('-e', '--email',
                    help='Email (optionnel)')

# Usage:
# python script.py -u alice -p secret123        # OK
# python script.py --username alice             # Erreur: -p requis
# python script.py                              # Erreur: -u et -p requis


[OK] 9. CHOIX LIMITÉS (CHOICES)

parser = argparse.ArgumentParser()

# Choix pour string
parser.add_argument('--format', choices=['json', 'xml', 'csv'],
                    help='Format de sortie')

# Choix pour nombres
parser.add_argument('--level', type=int, choices=[1, 2, 3, 4, 5],
                    help='Niveau de difficulté (1-5)')

# Choix pour enum
from enum import Enum

class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

parser.add_argument('--color', type=Color, choices=Color,
                    help='Couleur')

# Usage:
# python script.py --format json        # OK
# python script.py --format pdf         # Erreur: choix invalide
# python script.py --level 3            # OK
# python script.py --level 10           # Erreur: choix invalide


[OK] 10. NOMBRE VARIABLE D'ARGUMENTS (NARGS)

parser = argparse.ArgumentParser()

# 1. nargs='?' - 0 ou 1 argument
parser.add_argument('--output', nargs='?', default='stdout',
                    const='output.txt',
                    help='Fichier de sortie')
# Usage: python script.py              -> args.output = 'stdout'
#        python script.py --output      -> args.output = 'output.txt'
#        python script.py --output f.txt -> args.output = 'f.txt'

# 2. nargs='*' - 0 ou plusieurs arguments (liste)
parser.add_argument('files', nargs='*',
                    help='Fichiers à traiter')
# Usage: python script.py              -> args.files = []
#        python script.py f1.txt        -> args.files = ['f1.txt']
#        python script.py f1 f2 f3      -> args.files = ['f1', 'f2', 'f3']

# 3. nargs='+' - 1 ou plusieurs arguments (liste)
parser.add_argument('files', nargs='+',
                    help='Au moins un fichier requis')
# Usage: python script.py              -> Erreur
#        python script.py f1.txt        -> args.files = ['f1.txt']
#        python script.py f1 f2         -> args.files = ['f1', 'f2']

# 4. nargs=N - Exactement N arguments
parser.add_argument('--point', nargs=2, type=float,
                    help='Coordonnées x y')
# Usage: python script.py --point 10.5 20.3
#        args.point = [10.5, 20.3]

# 5. nargs=argparse.REMAINDER - Tous les arguments restants
parser.add_argument('command')
parser.add_argument('args', nargs=argparse.REMAINDER,
                    help='Arguments pour la commande')
# Usage: python script.py run --verbose --debug file.txt
#        args.command = 'run'
#        args.args = ['--verbose', '--debug', 'file.txt']


[OK] 11. METAVAR - PERSONNALISER L'AFFICHAGE

parser = argparse.ArgumentParser()

# Sans metavar
parser.add_argument('-f', '--file', help='Fichier source')
# Affiche: -f FILE, --file FILE

# Avec metavar
parser.add_argument('-f', '--file', metavar='FICHIER',
                    help='Fichier source')
# Affiche: -f FICHIER, --file FICHIER

# Metavar pour plusieurs arguments
parser.add_argument('--range', nargs=2, type=int,
                    metavar=('MIN', 'MAX'),
                    help='Plage de valeurs')
# Affiche: --range MIN MAX

# Exemple complet
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', metavar='ENTRÉE',
                    help='Fichier d\'entrée')
parser.add_argument('-o', '--output', metavar='SORTIE',
                    help='Fichier de sortie')
parser.add_argument('--size', nargs=2, type=int,
                    metavar=('LARGEUR', 'HAUTEUR'),
                    help='Dimensions')


[OK] 12. DEST - NOM DE L'ATTRIBUT

parser = argparse.ArgumentParser()

# Par défaut, le nom vient de l'argument
parser.add_argument('--input-file')  
# Accessible via: args.input_file (tirets -> underscores)

# Personnaliser le nom avec dest
parser.add_argument('--input-file', dest='source',
                    help='Fichier source')
# Accessible via: args.source

# Utile pour regrouper plusieurs options
parser.add_argument('-v', '--verbose', dest='verbosity',
                    action='store_true')
parser.add_argument('-q', '--quiet', dest='verbosity',
                    action='store_false')
# args.verbosity sera True ou False

# Exemple pratique
parser = argparse.ArgumentParser()
parser.add_argument('-i', dest='input_file')
parser.add_argument('-o', dest='output_file')

args = parser.parse_args(['-i', 'in.txt', '-o', 'out.txt'])
print(args.input_file)   # 'in.txt'
print(args.output_file)  # 'out.txt'


[OK] 13. GROUPES D'ARGUMENTS

parser = argparse.ArgumentParser()

# 1. Groupe optionnel (organisationnel seulement)
input_group = parser.add_argument_group('Options d\'entrée')
input_group.add_argument('-i', '--input', help='Fichier d\'entrée')
input_group.add_argument('-f', '--format', help='Format d\'entrée')

output_group = parser.add_argument_group('Options de sortie')
output_group.add_argument('-o', '--output', help='Fichier de sortie')
output_group.add_argument('--compress', action='store_true',
                         help='Compresser la sortie')

# 2. Groupe mutuellement exclusif (un seul à la fois)
group = parser.add_mutually_exclusive_group()
group.add_argument('-v', '--verbose', action='store_true',
                   help='Mode verbose')
group.add_argument('-q', '--quiet', action='store_true',
                   help='Mode silencieux')

# Usage:
# python script.py -v           # OK
# python script.py -q           # OK
# python script.py -v -q        # Erreur: mutuellement exclusifs

# Groupe mutuellement exclusif requis
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--create', action='store_true')
group.add_argument('--delete', action='store_true')
group.add_argument('--update', action='store_true')

# L'un des trois doit être fourni


[OK] 14. SOUS-COMMANDES (SUBPARSERS)
[OK] Créer des commandes comme git (add, commit, push)

parser = argparse.ArgumentParser(prog='git-like')
subparsers = parser.add_subparsers(dest='command', help='Commandes disponibles')

# Sous-commande 'add'
parser_add = subparsers.add_parser('add', help='Ajouter des fichiers')
parser_add.add_argument('files', nargs='+', help='Fichiers à ajouter')
parser_add.add_argument('-f', '--force', action='store_true',
                        help='Forcer l\'ajout')

# Sous-commande 'commit'
parser_commit = subparsers.add_parser('commit', help='Créer un commit')
parser_commit.add_argument('-m', '--message', required=True,
                          help='Message du commit')
parser_commit.add_argument('-a', '--all', action='store_true',
                          help='Commiter tous les changements')

# Sous-commande 'push'
parser_push = subparsers.add_parser('push', help='Pousser les commits')
parser_push.add_argument('remote', default='origin', nargs='?',
                        help='Remote (défaut: origin)')
parser_push.add_argument('branch', default='main', nargs='?',
                        help='Branche (défaut: main)')

# Usage:
args = parser.parse_args(['add', 'file1.txt', 'file2.txt', '-f'])
print(args.command)  # 'add'
print(args.files)    # ['file1.txt', 'file2.txt']
print(args.force)    # True

args = parser.parse_args(['commit', '-m', 'Initial commit', '-a'])
print(args.command)  # 'commit'
print(args.message)  # 'Initial commit'
print(args.all)      # True

# Dispatcher vers les fonctions
if args.command == 'add':
    do_add(args.files, args.force)
elif args.command == 'commit':
    do_commit(args.message, args.all)
elif args.command == 'push':
    do_push(args.remote, args.branch)


[OK] 15. SOUS-COMMANDES AVEC SET_DEFAULTS

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')

# Définir une fonction pour chaque commande
def cmd_start(args):
    print(f"Démarrage avec port {args.port}")

def cmd_stop(args):
    print("Arrêt du service")

def cmd_restart(args):
    print("Redémarrage...")

# Sous-commande avec fonction associée
parser_start = subparsers.add_parser('start')
parser_start.add_argument('-p', '--port', type=int, default=8000)
parser_start.set_defaults(func=cmd_start)

parser_stop = subparsers.add_parser('stop')
parser_stop.set_defaults(func=cmd_stop)

parser_restart = subparsers.add_parser('restart')
parser_restart.set_defaults(func=cmd_restart)

# Parsing et exécution automatique
args = parser.parse_args()
if hasattr(args, 'func'):
    args.func(args)
else:
    parser.print_help()


[OK] 16. VALEURS DEPUIS FICHIER (@)

# Créer un fichier d'arguments: args.txt
# --input data.txt
# --output result.txt
# --verbose

parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('--input')
parser.add_argument('--output')
parser.add_argument('--verbose', action='store_true')

# Charger depuis fichier
args = parser.parse_args(['@args.txt'])

# Équivaut à:
# args = parser.parse_args(['--input', 'data.txt', '--output', 'result.txt', '--verbose'])

# Mélanger fichier et ligne de commande
args = parser.parse_args(['@args.txt', '--verbose'])


[OK] 17. ARGUMENTS DEPUIS VARIABLES D'ENVIRONNEMENT

import os
import argparse

# Méthode manuelle
parser = argparse.ArgumentParser()
parser.add_argument('--api-key', 
                    default=os.environ.get('API_KEY'),
                    help='Clé API (ou variable API_KEY)')

# Avec fonction personnalisée
class EnvDefault(argparse.Action):
    """Action qui prend la valeur depuis env var si non fournie"""
    def __init__(self, envvar, required=True, default=None, **kwargs):
        if envvar in os.environ:
            default = os.environ[envvar]
        if required and default:
            required = False
        super().__init__(default=default, required=required, **kwargs)
    
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)

# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--api-key', action=EnvDefault, envvar='API_KEY',
                    help='Clé API')

# Priorité: ligne de commande > variable d'env > défaut


[OK] 18. VALIDATION PERSONNALISÉE

import argparse
import os

# Type personnalisé: fichier existant
def existing_file(filepath):
    """Valide que le fichier existe"""
    if not os.path.exists(filepath):
        raise argparse.ArgumentTypeError(f"Le fichier {filepath} n'existe pas")
    if not os.path.isfile(filepath):
        raise argparse.ArgumentTypeError(f"{filepath} n'est pas un fichier")
    return filepath

# Type personnalisé: email
import re

def email(value):
    """Valide un email"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    if not re.match(pattern, value):
        raise argparse.ArgumentTypeError(f"{value} n'est pas un email valide")
    return value

# Type personnalisé: plage de valeurs
def range_type(min_val, max_val):
    """Crée un type pour une plage de valeurs"""
    def check_range(value):
        ivalue = int(value)
        if ivalue < min_val or ivalue > max_val:
            raise argparse.ArgumentTypeError(
                f"{value} doit être entre {min_val} et {max_val}")
        return ivalue
    return check_range

# Type personnalisé: date
from datetime import datetime

def date_type(date_string):
    """Parse une date au format YYYY-MM-DD"""
    try:
        return datetime.strptime(date_string, '%Y-%m-%d').date()
    except ValueError:
        raise argparse.ArgumentTypeError(
            f"{date_string} n'est pas une date valide (format: YYYY-MM-DD)")

# Usage
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--file', type=existing_file,
                    help='Fichier existant')
parser.add_argument('-e', '--email', type=email,
                    help='Adresse email')
parser.add_argument('-p', '--port', type=range_type(1, 65535),
                    help='Port (1-65535)')
parser.add_argument('-d', '--date', type=date_type,
                    help='Date (YYYY-MM-DD)')


[OK] 19. FORMATAGE DE L'AIDE

parser = argparse.ArgumentParser(
    prog='mon_programme',
    description='Description détaillée du programme',
    epilog='Exemples d\'utilisation:\n'
           '  %(prog)s -i input.txt -o output.txt\n'
           '  %(prog)s --verbose --format json',
    formatter_class=argparse.RawDescriptionHelpFormatter,  # Préserve formatage
    add_help=True  # Ajoute -h/--help automatiquement (défaut)
)

# Formatters disponibles:
# 1. RawDescriptionHelpFormatter - Préserve sauts de ligne dans description
# 2. RawTextHelpFormatter - Préserve formatage partout
# 3. ArgumentDefaultsHelpFormatter - Affiche valeurs par défaut
# 4. MetavarTypeHelpFormatter - Utilise le type comme metavar

# Exemple avec ArgumentDefaultsHelpFormatter
parser = argparse.ArgumentParser(
    formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('-n', '--name', default='World',
                    help='Nom à saluer')
parser.add_argument('-c', '--count', type=int, default=1,
                    help='Nombre de répétitions')

# --help affichera:
# -n NAME, --name NAME  Nom à saluer (default: World)
# -c COUNT, --count COUNT  Nombre de répétitions (default: 1)


[OK] 20. PARSING PERSONNALISÉ

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('files', nargs='*')

# 1. parse_args() - Parse et quitte si erreur
args = parser.parse_args()

# 2. parse_known_args() - Parse ce qui est connu, ignore le reste
args, unknown = parser.parse_known_args()
print(args)     # Arguments connus
print(unknown)  # Liste des arguments inconnus

# 3. parse_args() avec liste personnalisée
args = parser.parse_args(['-v', 'file1.txt', 'file2.txt'])

# 4. parse_intermixed_args() - Arguments positionnels mélangés
parser = argparse.ArgumentParser()
parser.add_argument('command')
parser.add_argument('--verbose', action='store_true')
parser.add_argument('files', nargs='*')

# Permet: script.py run file1.txt --verbose file2.txt
args = parser.parse_intermixed_args()


[OK] 21. EXEMPLE COMPLET: UTILITAIRE DE FICHIERS

#!/usr/bin/env python3
"""
Utilitaire de traitement de fichiers
"""
import argparse
import sys
import os

def process_file(filename, options):
    """Traite un fichier selon les options"""
    print(f"Traitement de {filename}")
    if options.verbose:
        print(f"  Mode: {'lecture seule' if options.read_only else 'lecture/écriture'}")
        print(f"  Format: {options.format}")

def main():
    # Parser principal
    parser = argparse.ArgumentParser(
        prog='fileutil',
        description='Utilitaire de traitement de fichiers',
        epilog='Pour plus d\'aide sur une commande: %(prog)s COMMANDE --help',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    
    # Arguments globaux
    parser.add_argument('--version', action='version', version='%(prog)s 1.0.0')
    parser.add_argument('-v', '--verbose', action='store_true',
                        help='Mode verbose')
    
    # Sous-commandes
    subparsers = parser.add_subparsers(dest='command', help='Commandes disponibles')
    
    # Commande: process
    parser_process = subparsers.add_parser('process',
                                           help='Traiter des fichiers')
    parser_process.add_argument('files', nargs='+',
                                help='Fichiers à traiter')
    parser_process.add_argument('-f', '--format',
                                choices=['json', 'xml', 'csv'],
                                default='json',
                                help='Format de sortie')
    parser_process.add_argument('-r', '--read-only', action='store_true',
                                help='Mode lecture seule')
    
    # Commande: convert
    parser_convert = subparsers.add_parser('convert',
                                           help='Convertir des fichiers')
    parser_convert.add_argument('input', help='Fichier source')
    parser_convert.add_argument('output', help='Fichier destination')
    parser_convert.add_argument('--from-format', required=True,
                                choices=['json', 'xml', 'csv'],
                                help='Format source')
    parser_convert.add_argument('--to-format', required=True,
                                choices=['json', 'xml', 'csv'],
                                help='Format destination')
    
    # Commande: analyze
    parser_analyze = subparsers.add_parser('analyze',
                                           help='Analyser des fichiers')
    parser_analyze.add_argument('files', nargs='+',
                                help='Fichiers à analyser')
    group = parser_analyze.add_mutually_exclusive_group()
    group.add_argument('--detailed', action='store_true',
                       help='Analyse détaillée')
    group.add_argument('--summary', action='store_true',
                       help='Résumé seulement')
    
    # Parser les arguments
    args = parser.parse_args()
    
    # Dispatcher vers les commandes
    if args.command == 'process':
        for file in args.files:
            process_file(file, args)
    
    elif args.command == 'convert':
        print(f"Conversion: {args.input} ({args.from_format}) "
              f"-> {args.output} ({args.to_format})")
    
    elif args.command == 'analyze':
        mode = 'détaillée' if args.detailed else 'résumé' if args.summary else 'normale'
        print(f"Analyse {mode} de {len(args.files)} fichier(s)")
    
    else:
        parser.print_help()
        sys.exit(1)

if __name__ == '__main__':
    main()


[OK] 22. EXEMPLE COMPLET: OUTIL DE BASE DE DONNÉES

#!/usr/bin/env python3
"""
Outil de gestion de base de données
"""
import argparse

def connect_db(host, port, database, user, password):
    """Simule une connexion DB"""
    print(f"Connexion à {user}@{host}:{port}/{database}")
    return f"Connection({database})"

def cmd_query(args):
    """Exécute une requête"""
    conn = connect_db(args.host, args.port, args.database,
                     args.user, args.password)
    print(f"Exécution: {args.sql}")
    if args.output:
        print(f"Résultats sauvegardés dans: {args.output}")

def cmd_backup(args):
    """Sauvegarde la base"""
    conn = connect_db(args.host, args.port, args.database,
                     args.user, args.password)
    print(f"Sauvegarde vers: {args.output}")
    if args.compress:
        print("Compression activée")

def cmd_restore(args):
    """Restaure la base"""
    conn = connect_db(args.host, args.port, args.database,
                     args.user, args.password)
    print(f"Restauration depuis: {args.input}")

def main():
    parser = argparse.ArgumentParser(
        prog='dbtool',
        description='Outil de gestion de base de données'
    )
    
    # Arguments globaux (communs à toutes les sous-commandes)
    parser.add_argument('--host', default='localhost',
                        help='Hôte de la base de données')
    parser.add_argument('--port', type=int, default=5432,
                        help='Port de la base de données')
    parser.add_argument('-d', '--database', required=True,
                        help='Nom de la base de données')
    parser.add_argument('-u', '--user', required=True,
                        help='Nom d\'utilisateur')
    parser.add_argument('-p', '--password', required=True,
                        help='Mot de passe')
    parser.add_argument('-v', '--verbose', action='count', default=0,
                        help='Niveau de verbosité (-v, -vv, -vvv)')
    
    # Sous-commandes
    subparsers = parser.add_subparsers(dest='command', help='Commandes')
    
    # query
    parser_query = subparsers.add_parser('query', help='Exécuter une requête SQL')
    parser_query.add_argument('sql', help='Requête SQL')
    parser_query.add_argument('-o', '--output', help='Fichier de sortie')
    parser_query.set_defaults(func=cmd_query)
    
    # backup
    parser_backup = subparsers.add_parser('backup', help='Sauvegarder la base')
    parser_backup.add_argument('output', help='Fichier de sauvegarde')
    parser_backup.add_argument('-c', '--compress', action='store_true',
                               help='Compresser la sauvegarde')
    parser_backup.set_defaults(func=cmd_backup)
    
    # restore
    parser_restore = subparsers.add_parser('restore', help='Restaurer la base')
    parser_restore.add_argument('input', help='Fichier de sauvegarde')
    parser_restore.set_defaults(func=cmd_restore)
    
    # Parse
    args = parser.parse_args()
    
    # Exécuter la commande
    if hasattr(args, 'func'):
        args.func(args)
    else:
        parser.print_help()

if __name__ == '__main__':
    main()


[OK] 23. ARGUMENTS AVEC PRÉFIXE PERSONNALISÉ

# Changer le préfixe des options (par défaut: -)
parser = argparse.ArgumentParser(prefix_chars='+/')

# Maintenant les options utilisent + ou /
parser.add_argument('+v', '++verbose', action='store_true')
parser.add_argument('/o', '//output', help='Fichier de sortie')

# Usage: python script.py +v /o result.txt

# Mélanger plusieurs préfixes
parser = argparse.ArgumentParser(prefix_chars='-+')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('+q', '++quiet', action='store_true')


[OK] 24. CONFLITS D'OPTIONS

parser = argparse.ArgumentParser()

# Par défaut, argparse détecte les conflits
parser.add_argument('-v', '--verbose', action='store_true')
# parser.add_argument('-v', '--version')  # Erreur: -v déjà utilisé

# Résoudre les conflits
parser = argparse.ArgumentParser(conflict_handler='resolve')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-v', '--version')  # OK: remplace le premier -v

# Maintenant -v est pour --version, --verbose n'a plus de raccourci


[OK] 25. NAMESPACE PERSONNALISÉ

import argparse

# Namespace par défaut
parser = argparse.ArgumentParser()
parser.add_argument('--name', default='World')
args = parser.parse_args()
print(args.name)  # Attribut du namespace

# Namespace personnalisé (classe)
class Config:
    def __init__(self):
        self.debug = False
        self.timeout = 30

config = Config()
parser = argparse.ArgumentParser()
parser.add_argument('--name')
parser.add_argument('--debug', action='store_true')

# Parser dans l'objet existant
parser.parse_args(['--name', 'Alice', '--debug'], namespace=config)
print(config.name)     # 'Alice'
print(config.debug)    # True
print(config.timeout)  # 30 (valeur initiale préservée)

# Namespace avec dictionnaire
parser = argparse.ArgumentParser()
parser.add_argument('--key', default='value')
args = parser.parse_args()
print(vars(args))  # {'key': 'value'} - Convertir en dict


[OK] 26. PARSING EN DEUX ÉTAPES

# Cas d'usage: options globales puis sous-commandes

parser = argparse.ArgumentParser()
parser.add_argument('--config', help='Fichier de config')
parser.add_argument('--debug', action='store_true')

# Premier parsing: seulement les options connues
args, remaining = parser.parse_known_args()

# Charger la config
if args.config:
    load_config(args.config)

# Créer un nouveau parser avec plus d'options
parser2 = argparse.ArgumentParser()
parser2.add_argument('command')
parser2.add_argument('--verbose', action='store_true')

# Deuxième parsing avec les arguments restants
args2 = parser2.parse_args(remaining)

# Maintenant on a args.config, args.debug, args2.command, args2.verbose


[OK] 27. GESTION D'ERREURS PERSONNALISÉE

import argparse
import sys

class CustomArgumentParser(argparse.ArgumentParser):
    """Parser avec gestion d'erreurs personnalisée"""
    
    def error(self, message):
        """Override pour personnaliser les messages d'erreur"""
        sys.stderr.write(f'[X] Erreur: {message}\n\n')
        self.print_help(sys.stderr)
        sys.exit(2)

# Usage
parser = CustomArgumentParser(description='Mon programme')
parser.add_argument('--name', required=True)

# Si --name non fourni, affiche message personnalisé


# Capturer les erreurs sans quitter
parser = argparse.ArgumentParser()
parser.add_argument('--number', type=int, required=True)

try:
    args = parser.parse_args()
except SystemExit as e:
    if e.code == 2:  # Erreur de parsing
        print("Arguments invalides!")
        # Gérer l'erreur sans quitter
    else:
        raise


[OK] 28. EXEMPLES DE PATTERNS COURANTS

# Pattern 1: Mode debug/verbose avec niveaux
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='count', default=0,
                    help='Verbosité (-v: INFO, -vv: DEBUG, -vvv: TRACE)')

args = parser.parse_args()

import logging
if args.verbose == 0:
    level = logging.WARNING
elif args.verbose == 1:
    level = logging.INFO
elif args.verbose == 2:
    level = logging.DEBUG
else:
    level = logging.DEBUG  # Max verbosity

logging.basicConfig(level=level)


# Pattern 2: Mode dry-run
parser = argparse.ArgumentParser()
parser.add_argument('--dry-run', action='store_true',
                    help='Simule sans exécuter')
parser.add_argument('files', nargs='+')

args = parser.parse_args()
for file in args.files:
    if args.dry_run:
        print(f"[DRY-RUN] Traiterait: {file}")
    else:
        process_file(file)


# Pattern 3: Options yes/no explicites
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('--color', dest='use_color', action='store_true',
                   default=True, help='Activer les couleurs')
group.add_argument('--no-color', dest='use_color', action='store_false',
                   help='Désactiver les couleurs')


# Pattern 4: Configuration par défaut + override
import json

parser = argparse.ArgumentParser()
parser.add_argument('--config', help='Fichier de configuration')
parser.add_argument('--host', help='Override host')
parser.add_argument('--port', type=int, help='Override port')

args = parser.parse_args()

# Charger config par défaut
config = {'host': 'localhost', 'port': 8000}

# Charger depuis fichier si fourni
if args.config:
    with open(args.config) as f:
        config.update(json.load(f))

# Override avec arguments CLI
if args.host:
    config['host'] = args.host
if args.port:
    config['port'] = args.port


# Pattern 5: Répétition d'options pour liste
parser = argparse.ArgumentParser()
parser.add_argument('-I', '--include', action='append', default=[],
                    help='Répertoire à inclure (répétable)')

# Usage: python script.py -I dir1 -I dir2 -I dir3
args = parser.parse_args()
print(args.include)  # ['dir1', 'dir2', 'dir3']


# Pattern 6: Arguments de clé-valeur
def key_value_pair(arg):
    """Parse key=value"""
    try:
        key, value = arg.split('=', 1)
        return (key, value)
    except ValueError:
        raise argparse.ArgumentTypeError(
            f"{arg} n'est pas au format key=value")

parser = argparse.ArgumentParser()
parser.add_argument('-D', '--define', type=key_value_pair,
                    action='append', default=[],
                    help='Définir des variables (key=value)')

# Usage: python script.py -D name=Alice -D age=30
args = parser.parse_args()
variables = dict(args.define)
print(variables)  # {'name': 'Alice', 'age': '30'}


[OK] 29. INTÉGRATION AVEC LOGGING

import argparse
import logging

def setup_logging(args):
    """Configure le logging selon args.verbose"""
    if args.verbose >= 2:
        level = logging.DEBUG
    elif args.verbose == 1:
        level = logging.INFO
    else:
        level = logging.WARNING
    
    logging.basicConfig(
        level=level,
        format='%(asctime)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', action='count', default=0,
                        help='Verbosité')
    parser.add_argument('--log-file', help='Fichier de log')
    
    args = parser.parse_args()
    
    # Setup logging
    setup_logging(args)
    logger = logging.getLogger(__name__)
    
    if args.log_file:
        handler = logging.FileHandler(args.log_file)
        logger.addHandler(handler)
    
    # Utiliser le logger
    logger.debug("Mode debug activé")
    logger.info("Application démarrée")
    logger.warning("Attention!")


[OK] 30. TESTS UNITAIRES POUR ARGPARSE

import unittest
import argparse
from io import StringIO
import sys

class TestArgParse(unittest.TestCase):
    """Tests pour les arguments CLI"""
    
    def setUp(self):
        """Créer le parser pour chaque test"""
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('--name', required=True)
        self.parser.add_argument('--count', type=int, default=1)
        self.parser.add_argument('-v', '--verbose', action='store_true')
    
    def test_required_argument(self):
        """Test argument requis"""
        args = self.parser.parse_args(['--name', 'Alice'])
        self.assertEqual(args.name, 'Alice')
    
    def test_missing_required_argument(self):
        """Test argument requis manquant"""
        with self.assertRaises(SystemExit):
            self.parser.parse_args([])
    
    def test_optional_argument(self):
        """Test argument optionnel"""
        args = self.parser.parse_args(['--name', 'Bob', '--count', '5'])
        self.assertEqual(args.count, 5)
    
    def test_default_value(self):
        """Test valeur par défaut"""
        args = self.parser.parse_args(['--name', 'Charlie'])
        self.assertEqual(args.count, 1)
    
    def test_flag(self):
        """Test flag booléen"""
        args = self.parser.parse_args(['--name', 'Dave', '-v'])
        self.assertTrue(args.verbose)
    
    def test_type_validation(self):
        """Test validation de type"""
        with self.assertRaises(SystemExit):
            self.parser.parse_args(['--name', 'Eve', '--count', 'invalid'])

if __name__ == '__main__':
    unittest.main()


[OK] 31. ARGPARSE AVEC CONFIGPARSER

import argparse
import configparser

# Fichier config.ini:
# [DEFAULT]
# host = localhost
# port = 8000
# debug = false
#
# [database]
# host = db.example.com
# port = 5432

def load_config(config_file):
    """Charge la configuration depuis un fichier INI"""
    config = configparser.ConfigParser()
    config.read(config_file)
    return config

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--config', default='config.ini',
                        help='Fichier de configuration')
    parser.add_argument('--host', help='Override host')
    parser.add_argument('--port', type=int, help='Override port')
    parser.add_argument('--debug', action='store_true', help='Mode debug')
    
    args = parser.parse_args()
    
    # Charger depuis config file
    config = load_config(args.config)
    
    # Valeurs finales: CLI > config file > défaut
    host = args.host or config['DEFAULT'].get('host', 'localhost')
    port = args.port or config['DEFAULT'].getint('port', 8000)
    debug = args.debug or config['DEFAULT'].getboolean('debug', False)
    
    print(f"Host: {host}, Port: {port}, Debug: {debug}")

if __name__ == '__main__':
    main()


[OK] 32. ARGPARSE AVEC ENVIRONNEMENT

import argparse
import os

def env_or_required(key):
    """Rend un argument requis seulement si pas dans env"""
    return (
        {'default': os.environ.get(key)} if key in os.environ
        else {'required': True}
    )

parser = argparse.ArgumentParser()

# Si API_KEY existe dans l'env, pas requis en CLI
parser.add_argument('--api-key', **env_or_required('API_KEY'),
                    help='Clé API (ou variable API_KEY)')

# Si DB_HOST existe, utilise comme défaut
parser.add_argument('--db-host',
                    default=os.environ.get('DB_HOST', 'localhost'),
                    help='Hôte DB (ou variable DB_HOST)')

args = parser.parse_args()
print(f"API Key: {args.api_key}")
print(f"DB Host: {args.db_host}")


[OK] 33. BONNES PRATIQUES

# [OK] 1. Toujours fournir help pour chaque argument
parser.add_argument('--input', help='Fichier d\'entrée')  # [OK]
parser.add_argument('--output')  # [X] Pas d'aide

# [OK] 2. Utiliser des noms descriptifs
parser.add_argument('-i', '--input-file')  # [OK] Clair
parser.add_argument('-x')  # [X] Pas clair

# [OK] 3. Grouper les options courte et longue
parser.add_argument('-v', '--verbose')  # [OK]
parser.add_argument('-v')  # [X] Seulement courte
parser.add_argument('--verbose')  # [X] Seulement longue

# [OK] 4. Utiliser action='store_true' pour flags
parser.add_argument('--debug', action='store_true')  # [OK]
parser.add_argument('--debug', type=bool)  # [X] Ne fonctionne pas comme attendu

# [OK] 5. Valider les types tôt
parser.add_argument('--port', type=int)  # [OK] Validation automatique
# vs vérifier manuellement après

# [OK] 6. Utiliser choices pour limiter les valeurs
parser.add_argument('--format', choices=['json', 'xml'])  # [OK]
# vs valider manuellement

# [OK] 7. Fournir des valeurs par défaut sensées
parser.add_argument('--timeout', type=int, default=30)  # [OK]

# [OK] 8. Utiliser metavar pour clarifier
parser.add_argument('--range', nargs=2, metavar=('MIN', 'MAX'))  # [OK]

# [OK] 9. Organiser avec des groupes
group = parser.add_argument_group('Options de sortie')  # [OK]
# vs tout mélanger

# [OK] 10. Documenter dans le module docstring
"""
Script de traitement de fichiers.

Usage:
    python script.py input.txt -o output.txt
    python script.py *.txt --format json
"""

# [OK] 11. Gérer les erreurs gracieusement
try:
    args = parser.parse_args()
except SystemExit as e:
    # Gérer l'erreur
    pass

# [OK] 12. Tester les arguments
# Créer des tests unitaires pour vérifier le parsing


[OK] 34. RÉSUMÉ DES CONCEPTS CLÉS

"""
ARGPARSE = Parser professionnel d'arguments CLI

CRÉATION:
    parser = argparse.ArgumentParser(description='...')
    
TYPES D'ARGUMENTS:
    Positionnels: parser.add_argument('name')
    Optionnels: parser.add_argument('-n', '--name')
    
PARAMÈTRES IMPORTANTS:
    type: int, float, str, FileType, fonction custom
    action: store, store_true, store_false, append, count
    default: valeur par défaut
    required: True/False (pour optionnels)
    choices: liste de valeurs valides
    nargs: ?, *, +, N, REMAINDER
    help: texte d'aide
    metavar: nom affiché dans l'aide
    dest: nom de l'attribut
    
FONCTIONNALITÉS AVANCÉES:
    - Groupes d'arguments (organisationnel)
    - Groupes mutuellement exclusifs
    - Sous-commandes (subparsers)
    - Validation personnalisée (type=fonction)
    - Chargement depuis fichier (@args.txt)
    - Variables d'environnement
    
PARSING:
    args = parser.parse_args()
    args, unknown = parser.parse_known_args()
    
BONNES PRATIQUES:
    [OK] Toujours fournir help
    [OK] Utiliser -x/--xxx ensemble
    [OK] Types et validation tôt
    [OK] Valeurs par défaut sensées
    [OK] Organiser avec groupes
    [OK] Tester le parsing
    [OK] Documenter l'usage
    
USE CASES:
    • Scripts CLI professionnels
    • Outils en ligne de commande
    • Automatisation
    • Pipelines de données
    • Administration système
    • Applications avec sous-commandes (git-like)
"""


[OK] 35. ANTI-PATTERNS À ÉVITER

# [X] 1. Utiliser sys.argv directement pour parsing complexe
import sys
if len(sys.argv) > 1:
    filename = sys.argv[1]  # Fragile, pas de validation

# [OK] Utiliser argparse
parser.add_argument('filename')

# [X] 2. type=bool ne fait pas ce qu'on pense
parser.add_argument('--flag', type=bool)  # Ne fonctionne pas!
# bool('False') retourne True!

# [OK] Utiliser action pour booléens
parser.add_argument('--flag', action='store_true')

# [X] 3. Oublier de spécifier le type
parser.add_argument('--count')  # Sera une string!
result = args.count + 1  # Erreur TypeError

# [OK] Spécifier le type
parser.add_argument('--count', type=int)

# [X] 4. Validation manuelle après parsing
args = parser.parse_args()
if args.port < 1 or args.port > 65535:
    print("Port invalide!")

# [OK] Valider pendant le parsing
def valid_port(value):
    ivalue = int(value)
    if not 1 <= ivalue <= 65535:
        raise argparse.ArgumentTypeError("Port invalide")
    return ivalue
parser.add_argument('--port', type=valid_port)

# [X] 5. Arguments positionnels non intuitifs
parser.add_argument('arg1')
parser.add_argument('arg2')
parser.add_argument('arg3')  # Confus!

# [OK] Utiliser des options nommées ou nargs
parser.add_argument('command')
parser.add_argument('files', nargs='+')

# [X] 6. Pas de --help
parser = argparse.ArgumentParser(add_help=False)  # Mauvaise idée!

# [OK] Garder --help (défaut)
parser = argparse.ArgumentParser()

# [X] 7. Messages d'erreur cryptiques
parser.add_argument('-x')  # Aucune aide

# [OK] Documentation claire
parser.add_argument('-x', '--exclude', 
                    help='Pattern à exclure (regex)')


[OK] CHECKLIST POUR UN BON CLI

"""
[WHITE_SQUARE] Description claire du programme
[WHITE_SQUARE] --help généré automatiquement
[WHITE_SQUARE] --version si applicable
[WHITE_SQUARE] Arguments nommés de façon intuitive
[WHITE_SQUARE] Options courtes (-x) ET longues (--xxx)
[WHITE_SQUARE] Messages d'aide pour tous les arguments
[WHITE_SQUARE] Types spécifiés (int, float, etc.)
[WHITE_SQUARE] Validation des entrées
[WHITE_SQUARE] Valeurs par défaut sensées
[WHITE_SQUARE] Choix limités avec choices si applicable
[WHITE_SQUARE] Groupes pour organiser les options
[WHITE_SQUARE] Sous-commandes pour actions multiples
[WHITE_SQUARE] Gestion d'erreurs gracieuse
[WHITE_SQUARE] Tests unitaires du parsing
[WHITE_SQUARE] Documentation d'usage (docstring ou README)
[WHITE_SQUARE] Exemples d'utilisation dans epilog
[WHITE_SQUARE] Support de --dry-run pour opérations destructives
[WHITE_SQUARE] Mode verbose (-v, -vv, -vvv)
[WHITE_SQUARE] Configuration via fichier ET CLI
[WHITE_SQUARE] Variables d'environnement supportées
[WHITE_SQUARE] Messages d'erreur clairs et utiles
"""