argparse在Python类中的验证

huangapple go评论86阅读模式
英文:

argparse validation in a python class

问题

我尝试使用面向对象的方法来处理我的Python代码,最终将其转换为由PyInstaller创建的.EXE文件。这个想法是从用户输入中传递一系列参数给程序,最终会变成类似于(myprogram.exe -secureFolder C:/Users -thisisacsvfile.csv -countyCode 01069 -utmZone 15)的形式。

我可以初始化一个类定义并传递参数,如下所示:

import argparse
import sys

class myprogram():
    def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
        self.secureFolder = secureFolder
        self.inputCsvFile = inputCsvFile
        self.countyCode = countyCode
        self.utmZone = utmZone


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("secureFolder", help="A directory where the files are located", type=str)
    parser.add_argument("inputCsvFile", help="A CSV file containing the results for a particular county (e.g. 36107.csv)", type=str)
    parser.add_argument("countyCode", help="The FIPS county code", type=str)
    parser.add_argument("utmZone", help="The UTM zone code for that specific county (e.g. 18)", type=int)

然而,我需要验证每个用户参数,这是我感到困惑的部分。换句话说,我需要检查secureFolder是否存在,inputCsvFile是否确实是CSV文件并且包含特定列以及其他操作来验证其余的参数。我不确定这些操作应该在哪里执行?在类定义之后还是之前?

在采用面向对象的方法之前,我是这样做的:

# 检查是否传递了所有参数
undefined_arguments = [attr for attr in vars(args) if getattr(args, attr) is None]
if undefined_arguments:
    print("以下参数未定义:", undefined_arguments)
else:
    print("所有参数都已定义。")

# 1a. 检查inputCsvFile
if args.inputCsvFile is None:
    sys.exit("请选择要处理的输入CSV文件 (例如:inputCsvFile.../myfile.csv)")
else:
    if not os.path.isfile(args.inputCsvFile):
        sys.exit(f"文件 {args.inputCsvFile} 似乎不存在...请检查文件是否存在或您是否具有访问权限")
    else:
        grid_file_csv = args.inputCsvFile
        print(f"{args.inputCsvFile} 已找到...")

# 1b. 检查inputCsvFile是否为CSV文件
if not args.inputCsvFile.endswith('.csv'):
    raise ValueError("无效的输入文件。预期是一个CSV文件。")
    sys.exit('未传递正确的CSV文件...')

希望这可以帮助你理解在面向对象编程方法中应该如何验证参数。如果需要进一步的帮助,请随时提问。

英文:

I'm trying an OOP approach to my Python code which eventually will be converted to an .EXE file created with PyInstaller. The idea is to pass a series of arguments from the user input (n to a program that eventually will go something like (myprogram.exe -secureFolder C:/Users -thisisacsvfile.csv -countyCode 01069 -utmZone 15).

I can initialize a class definition and pass the arguments like:

import argparse
import sys

class myprogram():
    def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
        self.secureFolder = secureFolder
        self.inputCsvFile = inputCsvFile
        self.countyCode = countyCode
        self.utmZone = utmZone
        
        
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("secureFolder", help = "A directory where the files are located", type=str)
    parser.add_argument("inputCsvFile",  help="A CSV file containing the results for a particular county (e.g. 36107.csv)", type=str)
    parser.add_argument("countyCode", help = "The FIPS county code", type = str)
    parser.add_argument("utmZone", help = "The UTM zone code for that specific county (e.g. 18)", type = int)

However, I need to validate every user argument and that's the part where I'm getting confused. In other words, I need to check if the secureFolder exists, if the inputCsvFile is indeed a CSV and contains some specific columns and other operations for the rest of the arguments. What I don't know exactly, where do I perform these operations? After the class definition?
Before the OOP approach, I was doing something like:

# Check if all the arguments were passed
undefined_arguments = [attr for attr in vars(args) if getattr(args, attr) is None]
if undefined_arguments:
    print("The following arguments were not defined:", undefined_arguments)
else:
    print("All arguments were defined.")

# 1a. Check inputCsvFile
if args.inputCsvFile is None:
    sys.exit("Please select an input CSV file to process (e.g. inputCsvFile.../myfile.csv) ")
else:
    if not os.path.isfile(args.inputCsvFile):
        sys.exit (f"File {args.inputCsvFile} doesn't appear to exists...please check if the file exists or if you have privileges to access it")
    else:
        grid_file_csv = args.inputCsvFile 
        print (f"{args.inputCsvFile} found...")

# 1b. Check if inputCsvFile is a CSV:
if not args.inputCsvFile.endswith('.csv'):
    raise ValueError("Invalid input file. Expected a CSV file.")
    sys.exit('No propper CSV file has been passed...')

# 2. Check if the FIPS code
if args.countyCode is None:
   sys.exit("Please specify a valid county code (e.g. -countyCode3607)")
             
# Check the UTM area code
if args.utmzone is None:
   sys.exit("Please specify a valid UTM zone area (e.g. -utmZone 16): ")

if args.utmZone is not None:
    val = args.utmZone 
    if val < 1 and val > 20:
        raise Exception('UTM zone area should be between 1 and 20')
        sys.exit()

答案1

得分: 1

这主要是一个风格和偏好的问题。在我看来,尽可能在构建任何类之前都应该验证构建该类所需的值 - 尤其是当它依赖于用户输入时。

所以,获取命令行参数,验证它们,然后构建你的类。类似于这样:

import argparse
from sys import stderr
import os


class myprogram():
    def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
        self.secureFolder = secureFolder
        self.inputCsvFile = inputCsvFile
        self.countyCode = countyCode
        self.utmZone = utmZone

    def __str__(self):
        return f'{self.secureFolder=}, {self.inputCsvFile=}, {self.countyCode=}, {self.utmZone=}'
    
    @staticmethod
    def validate(ns):
        if not os.path.isdir(ns.secureFolder):
            print(f'{ns.secureFolder} is not a valid folder', file=stderr)
            return None
        try:
            with open(ns.inputCsvFile) as _:
                ...
        except Exception as e:
            print(f'Unable to open {ns.inputCsvFile} due to {e}', file=stderr)
            return None
        if ns.countyCode is None:
            print(f'{ns.countyCode} is an invalid county code', file=stderr)
            return None
        if (z := ns.utmZone) is None or z < 1 or z > 60:
            print(f'{z} is not a valid utmZone', file=stderr)
            return None
        return myprogram(ns.secureFolder, ns.inputCsvFile, ns.countyCode, ns.utmZone)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    cli = [
        ('-secureFolder', 'A directory where the files are located', str),
        ('-inputCsvFile', 'A CSV file containing the results for a particular county (e.g., 36107.csv)', str),
        ('-countyCode', 'The FIPS county code', str),
        ('-utmZone', 'The UTM zone code for that specific county (e.g., 18)', int)
    ]
    for c, h, t in cli:
        parser.add_argument(c, help=h, type=t)

    args = parser.parse_args()

    if (mp := myprogram.validate(args)) is None:
        print('Unable to construct class instance')
    else:
        # 此时我们有一个有效的myprogram类实例(mp)
        print(mp)

希望这对你有所帮助。

英文:

This is largely a question of style and preference. In my view, wherever possible, values needed to construct any class should be validated before the class is constructed - especially when it relies on user input.

So, get the command line arguments, validate them, then construct your class. Something like this:

import argparse
from sys import stderr
import os
class myprogram():
def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
self.secureFolder = secureFolder
self.inputCsvFile = inputCsvFile
self.countyCode = countyCode
self.utmZone = utmZone
def __str__(self):
return f&#39;{self.secureFolder=}, {self.inputCsvFile=}, {self.countyCode=}, {self.utmZone=}&#39;
@staticmethod
def validate(ns):
if not os.path.isdir(ns.secureFolder):
print(f&#39;{ns.secureFolder} is not a valid folder&#39;, file=stderr)
return None
try:
with open(ns.inputCsvFile) as _:
...
except Exception as e:
print(f&#39;Unable to open {ns.inputCsvFile} due to {e}&#39;, file=stderr)
return None
if ns.countyCode is None:
print(f&#39;{ns.countyCode} is an invalid county code&#39;, file=stderr)
return None
if (z := ns.utmZone) is None or z &lt; 1 or z &gt; 60:
print(f&#39;{z} is not a valid utmZone&#39;, file=stderr)
return None
return myprogram(ns.secureFolder, ns.inputCsvFile, ns.countyCode, ns.utmZone)
if __name__ == &#39;__main__&#39;:
parser = argparse.ArgumentParser()
cli = [
(&#39;-secureFolder&#39;, &#39;A directory where the files are located&#39;, str),
(&#39;-inputCsvFile&#39;, &#39;A CSV file containing the results for a particular county (e.g., 36107.csv)&#39;, str),
(&#39;-countyCode&#39;, &#39;The FIPS county code&#39;, str),
(&#39;-utmZone&#39;, &#39;The UTM zone code for that specific county (e.g., 18)&#39;, int)
]
for c, h, t in cli:
parser.add_argument(c, help=h, type=t)
args = parser.parse_args()
if (mp := myprogram.validate(args)) is None:
print(&#39;Unable to construct class instance&#39;)
else:
# at this point we have a valid myprogram class instance (mp)
print(mp)

答案2

得分: 0

argparse库可以为您执行一些验证操作,例如确保参数是必需的。对于其余部分,您可以编写小而简洁的验证函数:

import argparse
import pathlib

class MyProgram:
    def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
        self.secureFolder = secureFolder
        self.inputCsvFile = inputCsvFile
        self.countyCode = countyCode
        self.utmZone = utmZone

def validate_dir(value):
    path = pathlib.Path(value)
    if not path.is_dir():
        raise ValueError()
    return value

def validate_csv(value):
    path = pathlib.Path(value)
    if not path.exists():
        raise ValueError()
    if path.suffix != ".csv":
        raise ValueError()
    return value

def validate_utm_zone(value):
    value = int(value)
    if not (1 <= value <= 20):
        raise ValueError()
    return value

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-secureFolder",
        help="文件所在的目录",
        type=validate_dir,
        required=True,
    )
    parser.add_argument(
        "-inputCsvFile",
        help="包含特定县的结果的CSV文件",
        type=validate_csv,
        required=True,
    )
    parser.add_argument("-countyCode", help="FIPS县代码", required=True)
    parser.add_argument(
        "-utmZone",
        help="特定县的UTM区域代码(例如,18)",
        type=validate_utm_zone,
        required=True,
    )

    options = parser.parse_args()
    print(options)
    my_program = MyProgram(
        secureFolder=options.secureFolder,
        inputCsvFile=options.inputCsvFile,
        countyCode=options.countyCode,
        utmZone=options.utmZone,
    )

以下是一些示例运行:

# 正常情况
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15 
Namespace(secureFolder='./myfolder', inputCsvFile='input.csv', countyCode='code-blah', utmZone=15)

# 没有提供任何参数
$ python3  main.py                                                                                   

# 目录/foobar不存在
$ python3  main.py -secureFolder /foobar -inputCsvFile input.csv -countyCode code-blah -utmZone 15

# 无效的utmZone
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone foo

# utmZone不在范围内
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 21 

# CSV文件不以.csv结尾
$ python3  main.py -secureFolder ./myfolder -inputCsvFile main.py -countyCode code-blah -utmZone 15

# csv文件不存在
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15

# 获取帮助
$ python3 main.py -h                                                                                 

英文:

The argparse library can do some validations for you, such as ensuring that the argument is required. For the rest, you can write small, concise validation functions:

import argparse
import pathlib


class MyProgram:
    def __init__(self, secureFolder, inputCsvFile, countyCode, utmZone):
        self.secureFolder = secureFolder
        self.inputCsvFile = inputCsvFile
        self.countyCode = countyCode
        self.utmZone = utmZone


def validate_dir(value):
    path = pathlib.Path(value)
    if not path.is_dir():
        raise ValueError()
    return value


def validate_csv(value):
    path = pathlib.Path(value)
    if not path.exists():
        raise ValueError()
    if path.suffix != &quot;.csv&quot;:
        raise ValueError()
    return value


def validate_utm_zone(value):
    value = int(value)
    if not (1 &lt;= value &lt;= 20):
        raise ValueError()
    return value


if __name__ == &quot;__main__&quot;:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        &quot;-secureFolder&quot;,
        help=&quot;A directory where the files are located&quot;,
        type=validate_dir,
        required=True,
    )
    parser.add_argument(
        &quot;-inputCsvFile&quot;,
        help=&quot;A CSV file containing the results for a particular county&quot;,
        type=validate_csv,
        required=True,
    )
    parser.add_argument(&quot;-countyCode&quot;, help=&quot;The FIPS county code&quot;, required=True)
    parser.add_argument(
        &quot;-utmZone&quot;,
        help=&quot;The UTM zone code for that specific county (e.g. 18)&quot;,
        type=validate_utm_zone,
        required=True,
    )

    options = parser.parse_args()
    print(options)
    my_program = MyProgram(
        secureFolder=options.secureFolder,
        inputCsvFile=options.inputCsvFile,
        countyCode=options.countyCode,
        utmZone=options.utmZone,
    )

Here are some sample runs

# Happy path
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15 
Namespace(secureFolder=&#39;./myfolder&#39;, inputCsvFile=&#39;input.csv&#39;, countyCode=&#39;code-blah&#39;, utmZone=15)

# None of the arguments supplied
$ python3  main.py                                                                                   
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: the following arguments are required: -secureFolder, -inputCsvFile, -countyCode, -utmZone

# Directory /foobar does not exist
$ python3  main.py -secureFolder /foobar -inputCsvFile input.csv -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -secureFolder: invalid validate_dir value: &#39;/foobar&#39;

# Invalid utmZone
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone foo
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -utmZone: invalid validate_utm_zone value: &#39;foo&#39;

# utmZone not in range
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 21 
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -utmZone: invalid validate_utm_zone value: &#39;21&#39;

# CSV file does not end with .csv
$ python3  main.py -secureFolder ./myfolder -inputCsvFile main.py -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -inputCsvFile: invalid validate_csv value: &#39;main.py&#39;

# csv does not exist
$ python3  main.py -secureFolder ./myfolder -inputCsvFile input.csv -countyCode code-blah -utmZone 15
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE
main.py: error: argument -inputCsvFile: invalid validate_csv value: &#39;input.csv&#39;

# Get help
$ python3 main.py -h                                                                                 
usage: main.py [-h] -secureFolder SECUREFOLDER -inputCsvFile INPUTCSVFILE -countyCode COUNTYCODE -utmZone UTMZONE

options:
  -h, --help            show this help message and exit
  -secureFolder SECUREFOLDER
                        A directory where the files are located
  -inputCsvFile INPUTCSVFILE
                        A CSV file containing the results for a particular county
  -countyCode COUNTYCODE
                        The FIPS county code
  -utmZone UTMZONE      The UTM zone code for that specific county (e.g. 18)

huangapple
  • 本文由 发表于 2023年7月3日 16:46:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76603188.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定