2012-07-10 12 views
37

Mój skrypt python musi odczytać pliki z katalogu przekazanego w linii poleceń. Zdefiniowałem typ readable_dir, jak poniżej, do użycia z argparse do sprawdzania, czy katalog przekazany w linii poleceń jest dostępny i czytelny. Dodatkowo, dla argumentu katalogu podano domyślną wartość (/ tmp/non_existent_dir w poniższym przykładzie). Problem polega na tym, że argparse wywołuje readable_dir() na wartości domyślnej nawet w sytuacji, gdy argument katalogu jest jawnie przekazywany w linii poleceń. To powoduje, że skrypt się psuje, ponieważ domyślna ścieżka/tmp/non_existent_dir nie istnieje w kontekście, w którym katalog jest jawnie przekazywany w linii poleceń. Mogę obejść ten problem, nie określając wartości domyślnej i czyniąc ten argument obowiązkowym, lub odraczając walidację na później w skrypcie, ale jest to bardziej eleganckie rozwiązanie, o którym ktoś jest świadomy?typy ścieżek katalogów z argparse

#!/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() 
+4

Przydatny przykład kodu. Raise powinno "podnieść argparse.ArgumentTypeError", ale w przeciwnym razie, wykopuję typ readable_dir. – mlissner

Odpowiedz

28

Można utworzyć niestandardową akcję zamiast typu:

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) 

Ale to wydaje się trochę podejrzany mi - jeśli katalog nie jest podana, to przechodzi bez czytelnego katalogu, który wydaje aby pokonać cel sprawdzenia, czy katalog jest dostępny w pierwszej kolejności.

Należy zauważyć, że jak wskazano w komentarzach, może być przyjemniej niż
raise argparse.ArgumentError(self, ...), a nie argparse.ArgumentTypeError.

EDIT

O ile mi wiadomo, nie ma sposobu, aby potwierdzić domyślny argument. Przypuszczam, że programiści argparse założyli, że jeśli podasz domyślną wartość, powinna ona być poprawna. Najszybszym i najłatwiejszym rozwiązaniem jest po prostu sprawdzenie argumentów natychmiast po ich przeanalizowaniu. Wygląda na to, że próbujesz uzyskać tymczasowy katalog, żeby wykonać trochę pracy. W takim przypadku można użyć modułu tempfile, aby uzyskać nowy katalog do pracy. Zaktualizowałem powyższą odpowiedź, aby to odzwierciedlić. Tworzę katalog tymczasowy, użyję go jako domyślnego argumentu (tempfile gwarantuje już, że tworzony katalog będzie zapisywalny), a następnie rejestruję go, aby został usunięty po wyjściu z programu.

+0

mgilson, musiałem zmienić prospective_dir = wartości [0] na prospective_dir = values.Bez tego została odebrana tylko pierwsza postać z kłótni. Twoje rozwiązanie działa, gdy przekazywany jest jawny argument (w tym przypadku wartość domyślna nie jest sprawdzana w tych okolicznościach). Jednak gdy żaden argument nie jest przekazywany, domyślna wartość NIE jest sprawdzana, co stanowi problem. – iruvar

+0

@cravoori - Jakiś powód, dla którego myślałem, że "wartości" będzie listą. Przypuszczam, że dzieje się to tylko po określeniu 'nargs = ...'. W każdym razie, nie sądzę, że istnieje sposób, aby przekonać argparse do sprawdzenia poprawności po sparsowaniu argumentów (o co tak naprawdę prosisz). Musisz to zrobić sam. Zaktualizowałem swój kod, aby zawsze istniał poprawny katalog do pracy, który zostanie usunięty po wyjściu z programu. (katalogi określone w wierszu poleceń nie zostaną usunięte). – mgilson

+0

uwaga: katalog temp został użyty tylko jako przykład – iruvar

11

Jeśli skrypt nie może pracować bez ważnego launch_directory to powinno być obowiązkową argument:

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

btw, należy użyć argparse.ArgumentTypeError zamiast Exception w readable_dir().

+2

argparse.ArgumentError (self, "ciąg znaków błędów") jest najlepszy ze wszystkich, jeśli chcesz, aby użytkownik zobaczył miły komunikat o błędzie zamiast śledzenia stosu. Aby uzyskać więcej informacji, zobacz: http://stackoverflow.com/questions/9881933/catching-argumenttypeerror-exception-from-custom-action – Skotch

+1

@Skotch: 'readable_dir' definiuje typ, więc" ArgumentTypeError "jest tutaj odpowiedni. Naprawiłem literówkę: action -> type – jfs

+0

J.F. Sebastian: Jestem pewien, że jest to akcja niestandardowa, o której mówimy (zobacz definicję readable_dir podaną powyżej przez mgilson, pochodzi z argparse.Action). Przekazanie niestandardowej akcji argarse jako typu nie zadziała (przynajmniej tak nie było, gdy próbowałem). – Apteryx

13

Przedłożyłem a patch for "path arguments" to the Python standard library mailing list kilka miesięcy temu.

Dzięki tej PathType klasie, można po prostu podać następujące rodzaje argumentów, aby dopasować tylko istniejący katalog - nic innego nie da się komunikat o błędzie:

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

Oto kod, który może być łatwo zmodyfikowane tak, aby wymagały określonych uprawnień do pliku/katalogu:

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 
Powiązane problemy