From dlenski at gmail.com Wed Jul 22 22:25:38 2015 From: dlenski at gmail.com (Dan Lenski) Date: Wed, 22 Jul 2015 20:25:38 +0000 (UTC) Subject: [stdlib-sig] argparse PathType class -- validate file/directory paths without opening them Message-ID: Hi, The standard argparse library supports a convenient FileType class (https://docs.python.org/3/library/argparse.html#filetype-objects) which automatically converts file paths to open filehandles. I've found that I frequently need to validate that arguments correspond to valid file or directory paths, but WITHOUT subsequently opening these paths as files. This is useful when the path has to be subsequently passed off to another program via subprocess, etc. To support this case, I wrote the following PathType class, which will validate the suitability of a path for the specified usage (e.g. directory that exists, file that doesn't exist, etc.) before returning it unchanged. Would this be useful for inclusion in the standard library? Any comments on style, parameter-naming, etc? Thanks, Dan Lenski # standalone pathtype.py 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 From dlenski at gmail.com Sat Jul 25 00:12:31 2015 From: dlenski at gmail.com (Dan Lenski) Date: Fri, 24 Jul 2015 22:12:31 +0000 (UTC) Subject: [stdlib-sig] argparse PathType class -- validate file/directory paths without opening them References: Message-ID: Dan Lenski writes: > To support this case, I wrote the following PathType class, which > will validate the suitability of a path for the specified usage > (e.g. directory that exists, file that doesn't exist, etc.) before > returning it unchanged. > > Would this be useful for inclusion in the standard library? Any comments > on style, parameter-naming, etc? I received a couple suggestions in terms of normalizing the path with os.path.normpath (I've made it automatic) and changing it to an absolute path (optional). Thanks, Dan Lenski # patharg.py class PathType(object): def __init__(self, exists=True, type='file', dash_ok=True, abs=False): '''exists: True: a path that does exist False: a path that does not exist, in a valid parent directory None: don't care, in a valid parent directory 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 abs: if True, path will be coerced to an absolute path''' 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 ArgumentTypeError('standard input/output (-) not allowed as directory path') elif self._type == 'symlink': raise ArgumentTypeError('standard input/output (-) not allowed as symlink path') elif not self._dash_ok: raise ArgumentTypeError('standard input/output (-) not allowed') else: np = os.path.abspath(string) if abs else os.path.normpath(string) e = os.path.exists(np) if self._exists==True: if not e: raise ArgumentTypeError("path does not exist: '%s'" % np) if self._type is None: pass elif self._type=='file': if not os.path.isfile(np): raise ArgumentTypeError("path is not a file: '%s'" % np) elif self._type=='symlink': if not os.path.symlink(np): raise ArgumentTypeError("path is not a symlink: '%s'" % np) elif self._type=='dir': if not os.path.isdir(np): raise ArgumentTypeError("path is not a directory: '%s'" % np) elif not self._type(np): raise ArgumentTypeError("path not valid: '%s'" % np) else: if self._exists==False and e: raise ArgumentTypeError("path exists: '%s'" % np) p = os.path.dirname(os.path.normpath(np)) or '.' if not os.path.isdir(p): raise ArgumentTypeError("parent path is not a directory: '%s'" % p) elif not os.path.exists(p): raise ArgumentTypeError("parent directory does not exist: '%s'" % p) return np