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()
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'{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:
# at this point we have a valid myprogram class instance (mp)
print(mp)