1

I am wanting to handle input of multiple sets of data from the command line, and I'm not sure how to do it. I am thinking that having multiple optional arguments with multiple positional arguments would work, but I don't know how to do it.

For example, I want to get data like:

{
  'id': 23423423
  'host': 'hostname'
  'port': 234
}

I want to be able to enter multiple sets on the command line. How might I do this?

My idea was to have a command line like:

python check.py -d 23423423 hostname.com 234 -d 22332322 hostname2.com 234

I have been looking at argparse to see about how this might be done, but how this might be done is eluding me.

martineau
  • 119,623
  • 25
  • 170
  • 301
PoDuck
  • 1,381
  • 15
  • 38
  • Does this answer your question? [Argparse, handle repeatable set of items](https://stackoverflow.com/questions/26269760/argparse-handle-repeatable-set-of-items) – match Dec 29 '21 at 20:52
  • I would also suggest that if you need to pass in structured data, then passing in json - either as a file, or a command-line string - might be a better option. – match Dec 29 '21 at 20:53
  • 1
    I would describe that as multiple sets of `optionals`, flagged arguments. With `append` and `nargs=3` you'll get a list of lists, which you can restructure after parsing. – hpaulj Dec 29 '21 at 21:11
  • @hpaulj That might be preferable to my answer, which tries to have `arparse` itself handle building the `dict`. – chepner Dec 29 '21 at 21:13
  • @match Unfortunately the point is to create a json file with the data. If the json existed, this wouldn't be necessary. I have people that are having difficulty structuring the data properly. – PoDuck Dec 29 '21 at 21:15

2 Answers2

2

This feels like it could be improved, but you can define a custom subclass of _AppendAction to take the 3 arguments and build a dict using them.

import argparse
p = argparse.ArgumentParser()


class Config(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string):
        id_ = int(values[0])
        port = int(values[2])
        d = {"id": id_, "host": values[1], "port": port}
        return super().__call__(parser, namespace, d, option_string)

p.add_argument("-d", nargs=3, action=Config, metavar=("ID", "HOST", "PORT"))

print(p.parse_args())

Then

$ python3 tmp.py -d 23423423 hostname.com 234 -d 22332322 hostname2.com 234
Namespace(d=[{'id': '23423423', 'host': 'hostname.com', 'port': 234}, {'id': '22332322', 'host': 'hostname2.com', 'port': 234}])

(I would have preferred something like

def Config(t):
    return {"id": t[0], "host": t[1], "port": int(t[2])}

p.add_argument("-d", nargs=3, action='append', type=Config)

where the act of aggregating the 3 arguments into a single object is separate from appending the resulting dict to the destination, but each individual argument is passed to the type separately, rather than the trio of strings being passed as a single tuple. Alas.)

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I like that this goes the extra mile and gives me the dict. I wasn't necessarily asking for that. This works great for me. The only thing I am wondering is if the help text can be fixed so it would say -d id host port instead of -d d d d. – PoDuck Dec 29 '21 at 21:42
  • Yes, it can. I'll update. – chepner Dec 29 '21 at 21:46
1

Can be done as follows.

import argparse
parser = argparse.ArgumentParser(description='')
parser.add_argument("-d", nargs="+", action='append')

args = parser.parse_args()
print(args.d)

Running python check.py -d 23423423 hostname.com 234 -d 22332322 hostname2.com 234 will be parsed as a list of lists as below.

[['23423423', 'hostname.com', '234'], ['22332322', 'hostname2.com', '234']]
cyborg
  • 554
  • 2
  • 7