web-dev-qa-db-fra.com

types de chemin de répertoire avec argparse

Mon script python doit lire les fichiers d'un répertoire passé sur la ligne de commande. J'ai défini un type readable_dir comme ci-dessous à utiliser avec argparse pour valider que le répertoire passé sur la ligne de commande est existant et lisible. De plus, une valeur par défaut (/ tmp/non_existent_dir dans l'exemple ci-dessous) a également été spécifiée pour l'argument de répertoire. Le problème ici est qu'argparse invoque readable_dir () sur la valeur par défaut même dans une situation où un argument de répertoire est transmis explicitement sur la ligne de commande. Cela provoque le script à supprimer car le chemin par défaut/tmp/non_existent_dir n'existe pas dans un contexte où un répertoire est explicitement transmis sur la ligne de commande. Je pourrais contourner ce problème en ne spécifiant pas de valeur par défaut et rendre cet argument obligatoire, ou en reportant la validation à plus tard dans le script, mais est-ce une solution plus élégante que quiconque connaît?

#!/usr/bin/python
import argparse
import os

def readable_dir(prospective_dir):
  if not os.path.isdir(prospective_dir):
    raise Exception("readable_dir:{0} is not a valid path".format(prospective_dir))
  if os.access(prospective_dir, os.R_OK):
    return prospective_dir
  else:
    raise Exception("readable_dir:{0} is not a readable dir".format(prospective_dir))

parser = argparse.ArgumentParser(description='test', fromfile_prefix_chars="@")
parser.add_argument('-l', '--launch_directory', type=readable_dir, default='/tmp/non_existent_dir')
args = parser.parse_args()
55
iruvar

Vous pouvez créer une action personnalisée au lieu d'un type:

import argparse
import os
import tempfile
import shutil
import atexit

class readable_dir(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        prospective_dir=values
        if not os.path.isdir(prospective_dir):
            raise argparse.ArgumentTypeError("readable_dir:{0} is not a valid path".format(prospective_dir))
        if os.access(prospective_dir, os.R_OK):
            setattr(namespace,self.dest,prospective_dir)
        else:
            raise argparse.ArgumentTypeError("readable_dir:{0} is not a readable dir".format(prospective_dir))

ldir = tempfile.mkdtemp()
atexit.register(lambda dir=ldir: shutil.rmtree(ldir))

parser = argparse.ArgumentParser(description='test', fromfile_prefix_chars="@")
parser.add_argument('-l', '--launch_directory', action=readable_dir, default=ldir)
args = parser.parse_args()
print (args)

Mais cela me semble un peu louche - si aucun répertoire n'est donné, il passe un répertoire non lisible qui semble aller à l'encontre du but de vérifier si le répertoire est accessible en premier lieu.

Notez que, comme indiqué dans les commentaires, il serait peut-être plus
raise argparse.ArgumentError(self, ...) plutôt que argparse.ArgumentTypeError.

MODIFIER

Pour autant que je sache, il n'y a aucun moyen de valider l'argument par défaut. Je suppose que les développeurs argparse ont simplement supposé que si vous fournissez une valeur par défaut, elle devrait être valide. La chose la plus rapide et la plus simple à faire ici est de simplement valider les arguments immédiatement après les avoir analysés. Il semble que vous essayiez simplement d'obtenir un répertoire temporaire pour faire un peu de travail. Si tel est le cas, vous pouvez utiliser le module tempfile pour obtenir un nouveau répertoire dans lequel travailler. J'ai mis à jour ma réponse ci-dessus pour refléter cela. Je crée un répertoire temporaire, je l'utilise comme argument par défaut (tempfile garantit déjà que le répertoire qu'il crée sera accessible en écriture) puis je l'enregistre pour qu'il soit supprimé à la fin de votre programme.

35
mgilson

J'ai soumis n correctif pour les "arguments de chemin" à la Python il y a quelques mois.

Avec cette classe PathType, vous pouvez simplement spécifier le type d'argument suivant pour correspondre uniquement à un répertoire existant - tout le reste donnera une erreur message:

type = PathType(exists=True, type='dir')

Voici le code, qui pourrait être facilement modifié pour exiger également des autorisations spécifiques de fichier/répertoire:

from argparse import ArgumentTypeError as err
import os

class PathType(object):
    def __init__(self, exists=True, type='file', dash_ok=True):
        '''exists:
                True: a path that does exist
                False: a path that does not exist, in a valid parent directory
                None: don't care
           type: file, dir, symlink, None, or a function returning True for valid paths
                None: don't care
           dash_ok: whether to allow "-" as stdin/stdout'''

        assert exists in (True, False, None)
        assert type in ('file','dir','symlink',None) or hasattr(type,'__call__')

        self._exists = exists
        self._type = type
        self._dash_ok = dash_ok

    def __call__(self, string):
        if string=='-':
            # the special argument "-" means sys.std{in,out}
            if self._type == 'dir':
                raise err('standard input/output (-) not allowed as directory path')
            Elif self._type == 'symlink':
                raise err('standard input/output (-) not allowed as symlink path')
            Elif not self._dash_ok:
                raise err('standard input/output (-) not allowed')
        else:
            e = os.path.exists(string)
            if self._exists==True:
                if not e:
                    raise err("path does not exist: '%s'" % string)

                if self._type is None:
                    pass
                Elif self._type=='file':
                    if not os.path.isfile(string):
                        raise err("path is not a file: '%s'" % string)
                Elif self._type=='symlink':
                    if not os.path.symlink(string):
                        raise err("path is not a symlink: '%s'" % string)
                Elif self._type=='dir':
                    if not os.path.isdir(string):
                        raise err("path is not a directory: '%s'" % string)
                Elif not self._type(string):
                    raise err("path not valid: '%s'" % string)
            else:
                if self._exists==False and e:
                    raise err("path exists: '%s'" % string)

                p = os.path.dirname(os.path.normpath(string)) or '.'
                if not os.path.isdir(p):
                    raise err("parent path is not a directory: '%s'" % p)
                Elif not os.path.exists(p):
                    raise err("parent directory does not exist: '%s'" % p)

        return string
25
Dan Lenski

Si votre script ne peut pas fonctionner sans un launch_directory Valide, alors il doit devenir un argument obligatoire:

parser.add_argument('launch_directory', type=readable_dir)

btw, vous devez utiliser argparse.ArgumentTypeError au lieu de Exception dans readable_dir().

13
jfs