user1595929
user1595929

Reputation: 1372

Argparser and complex types

I'd like to provide my user the possibility to add multiple complex objects, as example: a Library object would be:

To do so what I did is to set a parameter --library with an append action and add a type to the parameter so it reads it the right format, what lead to the following command line: --library "library_name=test;library_number=5", the library type function will test and do the work, but this is not realy a pretty way to do it, there is any other way to do what I want ?

I wanted to use the nargs like this:

--library test 5

this look better to me however the type function I want to associate cannot be a complex one as it is runed on each values of the returned array!

Any idea?


I should explain a little bit more what I'm trying to do. I'm working on a workflow manager engine. This one specify a property file where are define all parameters and this parameter list is parsed to build the right argparse object.

As example I can define my library parameter as define before: library_name.name = ... library_name.help = ... library_name.type = str library_name.required = True

library_number.name = ...
library_number.help = ...
library_number.type = int
library_number.required = True 
...

But I want this object set as many as library the user want so I could add to those options an append action what would work but the user would have to provide all inputs in the right order:

--library-name lib1 --library-number 1 --library-name lib2 --library-number 2

but this is not that easy for the user, what I did is:

--library "name=lib1;number=1" --library "name=lib2;number=2"

but this is not "pretty", I would prefer something like this but I still want to check if this is the right format:

--library lib1 1 --library lib2 2

Is that clearer?


I should explain a bit more, I changed what you give me so the user can --library library-name=test library-number=5, so all fields are not necessary provided, however I would like some of them to be required, but to do so I need to know once all parameters are checked if the required ones have been settled

    class MiltipleParameters(object):
        def __init__(self, types):
            self.types = types
            self.index = None
            self.__name__ = "MiltipleParameters"
        def __call__(self, arg):
            parts = arg.split("=")
            if not self.types.has_key(parts[0]):
                raise argparse.ArgumentTypeError(parts[0] + " is an invalid flag! Available ones are: "+", ".join(self.types.keys()))
            try:
                value = self.types[parts[0]](parts[1])
            except:
                raise argparse.ArgumentTypeError("invalid " + self.types[parts[0]].__name__ + " value: '" + parts[1] + "' for sub parameter '" + parts[0] + "'")
            self.index = parts[0]
            return (parts[0], value)

this type take a hash table with the param_name:type, I could add the required field so it can be checked. But I do need to know when it is over to check if the required params where set. Is it possible ?

Upvotes: 1

Views: 734

Answers (4)

MatthieuW
MatthieuW

Reputation: 2382

Another solution with a custom action:

import argparse

class LibAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        try: 
            lib = {'name': values[0], 'type': int(values[1])}
            if namespace.library:
                namespace.library.append(lib)
            else:
                namespace.library=[lib]
        except ValueError:
             parser.error("Problem with library: %s is not int"%(values[1])) 

parser = argparse.ArgumentParser()
parser.add_argument("--library", action = LibAction, nargs=2)
args = parser.parse_args()
print args     

You obtain:

Namespace(library=[{'type': 1, 'name': 'lib1'}, {'type': 2, 'name': 'lib2'}])

and some error handling if you use: --library libA a

Upvotes: 0

Bakuriu
Bakuriu

Reputation: 102039

You can create a very simple state machine:

class NargsTypeChecker(object):
    def __init__(self, types):
        self.types = types
        self.index = 0
    def __repr__(self):
        return self.types[self.index].__name__
    def __call__(self, arg):
        value = self.types[self.index](arg)
        self.index = (self.index + 1) % len(self.types)
        return value

Used as:

parser.add_argument('--library', nargs=3, type=NargsTypeChecker((int, float, str)))

And some error outputs:

$python3 complex_type.py --library lib 1 3.6
Namespace(library=['lib', 1, 3.6])
$python3 complex_type.py --library lib other 3.6
usage: complex_type.py [-h] [--library LIBRARY LIBRARY LIBRARY]
complex_type.py: error: argument --library: invalid int value: 'other'
$python3 complex_type.py --library lib 2 other
usage: complex_type.py [-h] [--library LIBRARY LIBRARY LIBRARY]
complex_type.py: error: argument --library: invalid float value: 'other'

Upvotes: 1

MatthieuW
MatthieuW

Reputation: 2382

If you can accept as a format: --library lib1:1 --library lib2:2

def libType(ls):
    name, num = ls.split(':')
    return name, int(num)

parser.add_argument("--library", action = "append", type=libType)
# you get: Namespace(library=[('lib1', 1), ('lib2', 2)])

If you prefer to stick with: --library lib1 1 --library lib2 2

parser.add_argument("--library", action = "append", nargs=2)
# you get: Namespace(library=[['lib1', '1'], ['lib2', '2']])

Then you have to perform additional type check to get int instead of str.

Upvotes: 0

jazzpi
jazzpi

Reputation: 1429

If your library names contain no spaces, you could simply use one string for that, then do

library = argstring.split()

This will return a list library with library[0] as the name and library[1] as number.

Upvotes: 0

Related Questions