Fork me on GitHub
#babashka-sci-dev
<
2023-11-16
>
borkdude16:11:55

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']));

lispyclouds18:11:30

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

lispyclouds18:11:49

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

borkdude18:11:45

The first value does show a subcommand is what I meant

lispyclouds18:11:48

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

borkdude18:11:24

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

lispyclouds20:11:26

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

lispyclouds20:11:53

the second case just tells you the args as kv pairs

lispyclouds20:11:35

also you cannot create multiple subparsers with the same dest

borkdude20:11:27

I added dest= in both cases right?

lispyclouds20:11:07

ah right, lemme take another look

lispyclouds20:11:48

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"]))

lispyclouds20:11:04

guess what happens 😛

lispyclouds20:11:32

ah no hold on

borkdude20:11:38

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

lispyclouds20:11:40

finding a better one

lispyclouds20:11:32

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

lispyclouds20:11:51

it was overwriting the key foo in your code

lispyclouds20:11:19

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

lispyclouds20:11:56

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

borkdude20:11:27

I'm confused

lispyclouds20:11:40

dont have the same dest as an arg

borkdude20:11:42

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

borkdude20:11:46

I don't care about the rest

lispyclouds20:11:02

you had dest as foo and an arg as foo too

borkdude20:11:04

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

lispyclouds20:11:14

the first foo got overwritten by the second

borkdude20:11:29

if I do this:

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

borkdude20:11:41

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

lispyclouds20:11:16

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

lispyclouds20:11:30

in this case it would be foo = sub1

lispyclouds20:11:51

if you had another subparser: foo = sub2

borkdude20:11:23

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

borkdude20:11:43

e.g. I want to run:

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

lispyclouds20:11:52

the imperativeness is the headache

borkdude20:11:55

and then see what the output is like

lispyclouds20:11:28

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"]))

lispyclouds20:11:47

does this help?

borkdude20:11:54

definitely!

borkdude20:11:05

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

lispyclouds20:11:28

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

lispyclouds20:11:59

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"]
    )
)

lispyclouds20:11:13

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

lispyclouds20:11:36

even though i wanted baz as the second thing

borkdude20:11:50

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

lispyclouds20:11:57

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

print(vars(res))

lispyclouds20:11:06

this gives you a normal dict

borkdude20:11:55

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

lispyclouds20:11:13

doesnt seem like it

borkdude20:11:44

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

lispyclouds20:11:26

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

borkdude20:11:43

TypeError: 'Namespace' object is not subscriptable
👍

borkdude20:11:55

oh sorry, I just meant the thumbs up

borkdude20:11:58

ignore the error

lispyclouds20:11:08

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.

lispyclouds20:11:59

all things following which_parser_l1 til which_parser_l2 are args to it

lispyclouds20:11:21

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

lispyclouds20:11:32

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

borkdude20:11:44

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

borkdude20:11:03

thanks for digging into this

1
lispyclouds20:11:16

lemme see what people say about conflicting option keys

borkdude20:11:55

yeah, like

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

lispyclouds20:11:34

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)

lispyclouds20:11:51

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

👍 1