example - python argparse argumentparser()
tipos de ruta de directorio con argparse (3)
Mi script python necesita leer archivos de un directorio pasado en la línea de comando. He definido un tipo de archivo legible como se muestra a continuación para usarlo con argparse para validar que el directorio pasado en la línea de comando es existente y legible. Además, también se ha especificado un valor predeterminado (/ tmp / non_existent_dir en el ejemplo a continuación) para el argumento del directorio. El problema aquí es que argparse invoca a readable_dir () en el valor predeterminado incluso en una situación donde un argumento de directorio se pasa explícitamente en la línea de comando. Esto provoca que la secuencia de comandos falle ya que la ruta predeterminada / tmp / non_existent_dir no existe en un contexto donde un directorio se pasa explícitamente en la línea de comando. Podría evitar esto al no especificar un valor predeterminado y hacer que este argumento sea obligatorio, o posponiendo la validación hasta más adelante en el guión, pero ¿es una solución más elegante que todos conocen?
#!/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()
Envié un parche para "argumentos de ruta" a la lista de correo de la biblioteca estándar de Python hace unos meses.
Con esta clase PathType
, puede simplemente especificar el siguiente tipo de argumento para hacer coincidir solo un directorio existente; cualquier otra cosa dará un mensaje de error:
type = PathType(exists=True, type=''dir'')
Aquí está el código, que podría modificarse fácilmente para requerir permisos específicos de archivos / directorios:
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
Puede crear una acción personalizada en lugar de un tipo:
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)
Pero esto me parece un poco sospechoso: si no se proporciona un directorio, pasa un directorio no legible que parece frustrar el propósito de verificar si el directorio es accesible en primer lugar.
Tenga en cuenta que, como se señaló en los comentarios, podría ser mejor
raise argparse.ArgumentError(self, ...)
lugar de argparse.ArgumentTypeError
.
EDITAR
Por lo que sé, no hay forma de validar el argumento predeterminado. Supongo que los desarrolladores de argparse
simplemente asumieron que si está proporcionando un valor predeterminado, entonces debería ser válido. Lo más rápido y más fácil de hacer aquí es simplemente validar los argumentos inmediatamente después de analizarlos. Parece que solo intentas obtener un directorio temporal para hacer algo de trabajo. Si ese es el caso, puede usar el módulo tempfile
para que tempfile
un nuevo directorio. tempfile
mi respuesta anterior para reflejar esto. Creo un directorio temporal, utilizo eso como el argumento predeterminado ( tempfile
ya garantiza que el directorio que crea será grabable) y luego lo registro para que se elimine cuando se cierre el programa.
Si su script no puede funcionar sin un launch_directory
válido, entonces se debe convertir en un argumento obligatorio:
parser.add_argument(''launch_directory'', type=readable_dir)
Por cierto, debería usar argparse.ArgumentTypeError
lugar de Exception
en argparse.ArgumentTypeError
readable_dir()
.