Welcome, guest | Sign In | My Account | Store | Cart

I am all about trying to make development easier and reduce mundane tasks, especially dealing with commandline args and getopt, OptParse, argparse sorry just not a fan. Argparse-cli is really cool if you want low level control. I just got tired of writing tons of conditionals, labels, limited type handling, and having to do all of it over and over for every commandline project so help end the madness I wrote PyOpts.

Python, 334 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
"""
PyOpts.py
Author: AJ Mayorga

A highlevel quick wrapper for simplifying commandline options/arg handling

FEATURES:

    - Generates shorts, longs and help/usage automatically

    - Allows for specification of required options/args or groups there of
      in addition to exclusionary opt/args or groups.

    - Configures default values and selects/"choices" 

    - Returns a simple dictionary with key of supplied key name and value of configured type


DETAILS AND SUCH:

  By passing a dict of args that are normally present & you will use in your code anyway. 
  The arg dict is given a key name & initialize each with its own config dict consisting of
  the following keys and values.
  
  PROJECT KEYS:
  
  About     is an about section that will be used as a header for 
            the generated usage called by -h/--help

  ExitOnErr if True when and error or exception is found in processing the opt/args
            an error message will be generated (if Quiet=False) followed by sys.exit()
  
  Quiet     toggles error/exception message output

  Minimum   the minimum number of required opt/args. This is for trivial commandline 
            requirements. 0 indicates no min required.

  
  All other provided keys are considered option keys the provided names for which will be used
  to derive shorts '-p' and longs '--port'. Each option key's value is initialized with a 
  configuration dict.

  CONFIG DICT:
  
  GID   group identifier for a vars used by REQ  Can be alpha/num value
 
  REQ   indicates variable requirement when not present False is default
          - True indicates an independent value must be supplied, False not necessary
          - When a list of GIDs is supplied the var requirment is True and 
            all group members without a '-' prefix must have asigned values.
            A GID prefixed with a '-' indicates an exclusionary var meaning if 
            if provided with a current var an error will occur (think xor) 

  VALUE  serves as default value and/or type cast reference for final value
         for all variables that are int, strings, bools or float.
            - When a list is provided it serves as a choice selection
              where the provided value must match one listed in the list.
         
  HELP   a short descriptive line that is returned along with an error message
         if an error is discovered related to the var. Also used to construct
         --help/-h usage output

  if all opts/args are received without error the config portion of of the ARGS dict
  is overwritten with the user supplied value cast to the type of the supplied 'VALUE'
  from the orig config.

  opts/args supplied by the user that are not configured are ignored.

  a list of mock debug values can be supplied (see SA below) for debugging providing
  this list overrides reading sys.argv[1:]

  
#####################################################################################################
"""



import sys, os, string

class PyOptsException(Exception):
       def __init__(self, value):
           self.parameter = value
       def __str__(self):
           return repr(self.parameter)

class PyOpts(PyOptsException):
    
    def __init__(self):
        self.SA         = None
        self.about      = '' 
        self.uhead      = ''
        self.ExitOnErr  = False 
        self.Quiet      = False
        self.help       = False   
        self.usage      = []
        self.Temp       = []
        self.Lost       = {}
        self.Groups     = {}
        self.OUT        = {}
        self.Found      = []
        self.Minimum    = 0
        self.Configs    = ['About','Quiet','ExitOnErr','Minimum','Missing']
        
        self.ErrorMsgs  = ['Missing Member: %s of Option Group %s',
                           'Invalid Arguement: %s For Option %s Valid Args Are: %s',
                           'Duplicate Long Name Found Check Key Name In Init Dict',
                           'No Members Assigned To Required Group: ',
                           'Error Missing Required Parameter : %s  %s',
                           '%s Options/Args Less Than Required Minimum %s',
                           'Exclusion Error Option %s Cannot Not Be Used With %s' ]
    
    def OnErr(self, ErrorNum, args):
       if not self.Quiet:
           print self.ErrorMsgs[ErrorNum] % args
       if self.ExitOnErr:
          sys.exit(1)
       return False
        
    def SL(self, v, z):
        d = self.Temp
        if z+v in d:
           if z == '-':
               short = (z+[x for x in string.letters if not z+x in d][0],
                        z+v.upper())[not z+v.upper() in d]
               self.Temp.append(short)
               return short
           raise PyOptsException(self.ErrorMsgs[2])
        long = z+v
        self.Temp.append(long)  
        return long
            
    def CastAs(self, var, varType):
        try:
            if   isinstance(varType, bool): return bool(var)
            elif isinstance(varType, str):  return str(var)
            elif isinstance(varType, int):  return int(var)
            elif isinstance(varType, float):return float(var)
            elif isinstance(varType, long): return long(var)
            elif isinstance(varType, int):  return int(var) 
        except:
            return False    
        
    def GetValue(self, opt,cV,aV):
        val = self.CastAs(aV, cV)
        if not isinstance(cV, list):
            return val
        for x in cV:
            y = self.GetValue(opt,x, aV)
            if repr(y) == repr(x):
                return y
        choices = '|'.join([str(x) for x in cV])
        self.OnErr(1,(repr(aV),opt,choices))
    
    def LookUp(self, d, key):
        for k,v in d.iteritems():
            if k in self.Configs: continue
            if k == key:
                return v
            elif isinstance(d[k], dict):
               r = self.LookUp(d[k], key)
               if r: return r
        return None
                           
    def DependencyCheck(self, Opts):
        for gid, members in self.Groups.iteritems():
            missing = [x for x in members if x not in self.Found]
            self.Groups[gid]['Missing'] = missing
        
        for opt, conf in Opts.iteritems():
           if opt in self.Configs:
               continue
           exclude = False
           if isinstance(conf['REQ'], list):
               for gid in conf['REQ']:
                   if isinstance(gid, int):
                       if gid < 0:
                           exclude=True
                           gid = abs(gid)
                   else:
                       if gid[0] == '-':
                           exclude=True
                           gid = gid.replace('-','')
                   gid = repr(gid)       
                   if self.Groups.has_key(gid):                   
                       groupOpts = repr([x for x in self.Groups[gid].itervalues() if x != []])
                       groupOpts = groupOpts.replace("'", "")
                       
                       if self.Groups[gid] and exclude:
                           opt = repr(self.LookUp(self.Groups, opt)).replace("'","")
                           self.OnErr(6, (opt,groupOpts))
                                      
                       if self.Groups[gid]['Missing'] != []:
                           mia = ''
                           for member in  self.Groups[gid]['Missing']:
                               mia += repr(self.Groups[gid][member]).replace("'","")
                               return mia,groupOpts
                   else:
                       print self.Groups
                       raise PyOptsException(self.ErrorMsgs[3]+gid)
        return False
                   
    def OP(self, Opts, SA=False):
        SA              = (SA,sys.argv[1:])[SA==False] 
        self.about      =  Opts.get('About', '')
        self.ExitOnErr  =  Opts.get('ExitOnErr', True)
        self.Quiet      =  Opts.get('Quiet', False)    
        self.Minimum    =  Opts.get('Minimum', 0)
        
        for opt, conf in Opts.iteritems():
            if opt in self.Configs:
                continue
            
            conf['GID']   = gid   = conf.get('GID', None)
            conf['REQ']   = req   = conf.get('REQ', False)
            conf['HELP']  = help  = conf.get('HELP', '---')
            conf['VALUE'] = value = conf.get('VALUE', '')
            conf['VALUE'] = value = (value,'True/False')[isinstance(value,bool)]
            
            short, long = (self.SL(opt[:1],'-'), self.SL(opt,'--'))
           
            self.usage.extend([' '+short+' '*2,long+' '*(15-len(long)),
                               str(value)+' '*(20-len(str(value))),help,'\n'])

            if gid != None:
                gid = repr(gid)
                if not self.Groups.has_key(gid):
                    self.Groups[gid]={}
                self.Groups[gid][opt] = [short,long]
               
            if not short in SA and not long in SA and req:
                 if gid:
                     self.Lost[gid] = opt
                 if req == True:
                     self.OnErr(4,(opt,help))
            
            for x in range(len(SA)):
                if SA[x] == '-h' or SA[x] == '--help':
                    self.help = True
                    continue
                if SA[x] == long or SA[x] == short:
                    self.OUT[opt] = self.GetValue(opt, value, SA[x+1])
                    self.Found.append(opt)
                        
        if len(self.Found) < self.Minimum:
            self.OnErr(5, (len(self.Found),self.Minimum))
             
        mia = self.DependencyCheck(Opts)
        if mia:
            self.OnErr(0,mia)
                   
        if self.help:
           print self.about+self.uhead+'\n'+''.join(self.usage)
           sys.exit(1)
   
        return self.OUT
    
#------------------------------------------------------------------------------#
# DEMO PROJECT STARTS HERE

from PyOpts import PyOpts


__author__  = "AJ Mayorga"
__version__ = "1.0.1"
__status__  = "Demo"
              
   
ABOUT="""
################################################################################
  project: myOpts.py                                                
                                                                    
  version : """+__version__+"""                                     
  author  : """+__author__+"""
  status  : """+__status__+"""
         
  This is a Demo Program To Illustrate Handling Of Options/Arguments 
  At The Commandline
         
################################################################################
"""
 
#For Debugging Play With These To Override sys.argv[1:] to see how things are handled

SA  = ['myOpts.py','--to','Mary','--fromname','boomers', 
       '--body','blah','--fromaddress', 'Billy@yuckbutt.com',
       '--subject', 'Scooters', '--threads',43, '--template','Monkey','-h'] 

 
class OPTIONS_DEMO:
    
    def __init__(self):
         
         #Simple Option/Args
         
         self.ARGS = {}
         self.ARGS['About']        = ABOUT
         self.ARGS['Minimum']      = 4
         self.ARGS['to']           = {'VALUE': "", 'HELP':"Email List or Single Address"}
         self.ARGS['fromaddress']  = {'VALUE': "", 'HELP':"Email Address of Sender e.g.WB@blah.com"}
         self.ARGS['subject']      = {'VALUE': "", 'HELP':"Subject of Email"}
         self.ARGS['body']         = {'VALUE': "", 'HELP':"Body/Content of Email (File or String)"}         
   
         """
         #Advanced Option/Args
         PROTOS = ["HTTP","HTTPS","FTP"]
         
         self.ARGS = {}
         self.ARGS['About']        = ABOUT
         self.ARGS['ExitOnErr']    = True
         self.ARGS['Quiet']        = False
         self.ARGS['Minimum']      = 0
         
         self.ARGS['newtemplate']  = {'GID':0, 'REQ':[-1],  'VALUE': False, 'HELP':"Save Resulting Email As New Template"}
         self.ARGS['template']     = {'GID':1, 'REQ':[-0],  'VALUE': False, 'HELP':"Email Template To Use"}
         self.ARGS['to']           = {'GID':2, 'REQ':True,  'VALUE': "",    'HELP':"Email List or Single Address"}
         self.ARGS['fromname']     = {'GID':3, 'REQ':[2],   'VALUE': "",    'HELP':"Name of Sender e.g. Will J Buck CEO"}
         self.ARGS['fromaddress']  = {'GID':4, 'REQ':[3],   'VALUE': "",    'HELP':"Email Address of Sender e.g. WB@blah.com"}
         self.ARGS['subject']      = {'GID':5, 'REQ':[4],   'VALUE': "",    'HELP':"Subject of Email"}
         self.ARGS['body']         = {'GID':6, 'REQ':[5],   'VALUE': "",    'HELP':"Body/Content of Email (File or String)"}
         self.ARGS['attachments']  = {         'REQ':False, 'VALUE': "",    'HELP':"Comma Separated List of Attachment Files"}
         self.ARGS['threads']      = {         'REQ':False, 'VALUE': 1,     'HELP':"Number of Sending Threads"}
         self.ARGS['signed']       = {         'REQ':False, 'VALUE': False, 'HELP':"Digitally Sign On Each Sent Email"}
         self.ARGS['readreceipt']  = {         'REQ':False, 'VALUE': False, 'HELP':"Require Read Receipts"}
         self.ARGS['linkprotocol'] = {         'REQ':False, 'VALUE': PROTOS,'HELP':"Format Embedded Links To Protocol"}
         """
         
         self.ARGS = PyOpts().OP(self.ARGS,SA)  
         
         print self.ARGS
    
      
if __name__ == '__main__':
    
    OD = OPTIONS_DEMO()
    
   

5 comments

Alan Franzoni 11 years, 6 months ago  # | flag

while optparse may show its age, argparse and argparse-cli would serve you better. Take a look at them, before reinventing the wheel :P

AJ. Mayorga (author) 11 years, 6 months ago  # | flag

Thx Alan, I did look at those and must say argparse-cli looks pretty cool tons of capability but it lacks a high level implementation for basic options/args needs Im all about granularity and control but sometimes you just dont want to type all those parser.add_options and have to do it for every commandline project BLEH!, And argparse is not in standard Python yet so Guessing what I need to do is actually make a high level wrapper for optparse/argparse, hmmmmm so many ideas so little time.

AJ. Mayorga (author) 11 years, 6 months ago  # | flag

Revised and even more simple uses common variables already found for the most part in your code to handle basic options handling, when you dont need all the bells and whistles of argparse and just want to get on coding. Provided values in the ARGS dict are used to determine type and default. after myOpts does the checking and processing it outputs a very sane easy dict that overwrites the orig so your program can just refer to class args by calling self.ARGS['argname'] = value with correct type from commandline, Easy : )

Alia Khouri 11 years, 6 months ago  # | flag

You may want to have at look at this recipe http://code.activestate.com/recipes/576935/ for a higher level interface to argparse.

AJ. Mayorga (author) 11 years, 6 months ago  # | flag

@Alia thanks looked at it but still doesnt seem as intuitive or streamlined as what Ive done here. with what ive done you never have to declare or instantiate anything that you wont use later. Granted pyopts doesnt give you as much granularity as argparse-cli but for fast option handling and a real descent set of flexibilities it comes pretty close in a small package.