babashka-sci-dev 2023-11-16

Who here is familiar with Python argparse? I'm trying to understand why the last printed value doesn't show a subcommand

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
subparser1 = subparsers.add_parser('1')
subparser1.add_argument('-x')
subparser2 = subparsers.add_parser('2')
subparser2.add_argument('y')
res = parser.parse_args(['2', 'frobble'])
print(res);

parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest='foo')
sub1 = sub.add_parser('sub1')
sub1.add_argument('--foo');
sub1_sub = sub1.add_subparsers(title='dude')
sub2 = sub1_sub.add_parser('sub2')
sub2.add_argument('--dude')
print(parser.parse_args(['sub1', '--foo', '1']))
#                          'sub2', '--dude', '2']));

You would get a Namespace object as an output, which is the result of the parsing. It’s a class subclassing Dict

Should’ve been just a plain Dict but python and overengineering is a thing. Python is easy, something something… 😒

The first value does show a subcommand is what I meant

What’s the value that you see? I’ll try to take a better look with a proper machine

$ python3 arg.py --dude
Namespace(subparser_name='2', y='frobble')
Namespace(foo='1')

if you add dest when creating a subparser its going to have that as a key in the namespace too. to help you identify which one was used

the second case just tells you the args as kv pairs

also you cannot create multiple subparsers with the same dest

I added dest= in both cases right?

ah right, lemme take another look

funny python: try

parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="foo")
sub1 = sub.add_parser("sub1")
sub1.add_argument("--bar")
print(parser.parse_args(["sub1", "--bar", "1"]))

guess what happens 😛

ah no hold on

it crashes because it doesn't know bar would be my guess

finding a better one

yes this is good. Namespace(foo='sub1', bar='1')

it was overwriting the key foo in your code

because foo is both a valid arg and the name of subparser dest

if you pass --bar then you have the expected behaviour

I'm confused

dont have the same dest as an arg

all I want is to find out the nest subparser output from this stuff

I don't care about the rest

you had dest as foo and an arg as foo too

I don't even want to know what "dest" is

the first foo got overwritten by the second

if I do this:

sub = parser.add_subparsers(dest='foo')
sub1 = sub.add_parser('sub1')
what does it even mean

I thought sub1 was the subcommand, but is foo the subcommand?

no foo is the key in the namespace where the subcommand will be stored

in this case it would be foo = sub1

if you had another subparser: foo = sub2

can you maybe just write a program that has nested subcommand behavior that I can run? I just don't get this API

e.g. I want to run:

print(parser.parse_args(['sub1', '--bar', '1','sub2', '--dude', '2']));

the imperativeness is the headache

and then see what the output is like

import argparse


parser = argparse.ArgumentParser()

subparsers1 = parser.add_subparsers(dest="which_parser_l1")

sub1 = subparsers1.add_parser("sub1")
sub1.add_argument("--bar")

subparsers2 = sub1.add_subparsers(dest="which_parser_l2")
sub2 = subparsers2.add_parser("sub2")
sub2.add_argument("--dude")

print(parser.parse_args(["sub1", "--bar", "1", "sub2", "--dude", "2"]))

does this help?

definitely!

and how does one get, say, the options for both subcommands out of this?

if they have the same keys the last one will be there

import argparse


parser = argparse.ArgumentParser()

subparsers1 = parser.add_subparsers(dest="which_parser_l1")

sub1 = subparsers1.add_parser("sub1")
sub1.add_argument("--bar")
sub1.add_argument("--baz")

subparsers2 = sub1.add_subparsers(dest="which_parser_l2")
sub2 = subparsers2.add_parser("sub2")
sub2.add_argument("--dude")
sub2.add_argument("--baz")

print(
    parser.parse_args(
        ["sub1", "--bar", "1", "--baz", "2", "sub2", "--dude", "2", "--baz", "4"]
    )
)

Namespace(which_parser_l1='sub1', bar='1', baz='4', which_parser_l2='sub2', dude='2')

even though i wanted baz as the second thing

but how do you get stuff out of this? is this data-ish?

res = parser.parse_args(
    ["sub1", "--bar", "1", "--baz", "2", "sub2", "--dude", "2", "--baz", "4"]
)

print(vars(res))

this gives you a normal dict

it lumps all the options in one dict? I thought you would be able to get the opts out per subcommand or so

doesnt seem like it

ok, and how do normal python people get data out of this, without calling "vars"?

res.arg_name

lemme read a bit more about nested things, i could be missing something

TypeError: 'Namespace' object is not subscriptable
👍

oh sorry, I just meant the thumbs up

ignore the error

right so it seems its in the order of the add_subparser calls. thats the order in which its going to form the keys in the namespace object.

all things following which_parser_l1 til which_parser_l2 are args to it

dicts in python maintain insertion order by default so that works too i suppose

for k, v in vars(res).items():
    print(k, v)

ok, still conflicts between the option keys, but it is possible to distuingish which group stuff was added to, got it

thanks for digging into this

👍🏾 1

lemme see what people say about conflicting option keys

yeah, like

--debug true sub1 --debug false sub2 --debug true

thats the solution it seems:

import argparse


parser = argparse.ArgumentParser()

subparsers1 = parser.add_subparsers(dest="which_parser_l1")

sub1 = subparsers1.add_parser("sub1")
sub1.add_argument("--bar")
sub1.add_argument("--baz")

subparsers2 = sub1.add_subparsers(dest="which_parser_l2")
sub2 = subparsers2.add_parser("sub2")
sub2.add_argument("--dude")
sub2.add_argument("--baz", dest="sub_baz")

res = parser.parse_args(
    ["sub1", "--bar", "1", "--baz", "2", "sub2", "--dude", "2", "--baz", "4"]
)

print(res)

Namespace(which_parser_l1='sub1', bar='1', baz='2', which_parser_l2='sub2', dude='2', sub_baz='4')

👍 1