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()
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 plusraise 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.
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
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()
.