this post was submitted on 23 Aug 2023
26 points (93.3% liked)

Programming

17666 readers
368 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities [email protected]



founded 2 years ago
MODERATORS
 

This is solved, I was being dumb. Please see second EDIT below

This isn't really language specific, but if it helps I'm using Python. I can get the parameters just fine with the Traitlets module, but I'm still a novice really and figuring out which patterns to use is challenging.

Say you have a bunch of command line parameters. Some are booleans, where their presence means True, absence means False. Other parameters must accept one text string, and others can be used multiple times to build a list of strings.

It feels inefficient/wrong to use a bunch of IF/THEN/ELSE statements to decide what to do with the parameters, and prone to edge case errors. Is there a pattern that would invoke the correct method based on the combination of input parameters?

Examples:

app thing --dry-run --create --name=newname01 --name=newname02 --verbose

app thing --create --name=newname01 --name=newname02 --name=newname03

app job --cancel -i 01 -i 02 -i 03

EDIT: Via the Traitlets module, I get those arguments parsed and accessible as self.argname, so getting them into my app is working great. It's just deciding what to do with them that is confusing me.

Thank you, sorry for my noobness.

EDIT2: I think I understand where I'm going wrong:

I'm creating subcommands based on objects, not actions. i.e. appname thing --action should really be appname action --thing. Once things are divided up into actions, assigning things to those actions will be much, much easier and straightforward.

Sorry for a confusing and fairly pointless post :(

all 22 comments
sorted by: hot top controversial new old
[–] [email protected] 18 points 1 year ago (2 children)

As a few others mentioned, the argparse module from the stdlib is the way to go for Python. It takes care of most of this for you in an optimized way. Here's a minimal example of the above:

#!/bin/env python3

import argparse


# Setup main parser
parser = argparse.ArgumentParser(
    prog="app",
    description="App that manages things and jobs",
)
parser.add_argument(
    "-v",
    "--verbose",
    action="store_true",
)
subparsers = parser.add_subparser(
    help="Sub commands",
    dest="command",
)

# Setup sub-parsers
thing_parser = subparsers.add_parser(
    "thing",
    help="Manage things",
)
# thing_parser args
thing_parser.add_argument(
    "--dry_run",
    action="store_true",
    help="dry run",
)
thing_parser.add_argument(
    "--create",
    action="store_true",
    help="Create thing",
)
thing_parser.add_argument(
    "--name",
    action="store",
    type=str,
    required=True,
    nargs="+",
    help="Thing name",
)

job_parser = subparsers.add_parser(
    "job",
    help="Manage jobs",
)
job_parser.add_argument(
    "--cancel",
    "-c",
    action="store_true",
    help="Cancel job",
)
job_parser.add_argument(
    "--index",
    "-i",
    action="store",
    type=int,
    required=True,
    nargs="+",
    help="Job index",
)


def main():
    args=parser.parse_args()
    if args.verbose:
        print("Verbose mode")
    # Match-Case statement for cleaner logic
    match args.command:
        case "thing":
            # thing logic and function calls here
        case "job":
            # job logic and function calls here
        case _:
            parser.print_usage()

if __name__ == "__main__":
    main()
[–] bloopernova 8 points 1 year ago (1 children)

That's an awesome answer, thank you very much. It's much more elegant than my stuff!

[–] [email protected] 3 points 1 year ago (1 children)

You're very welcome! I've spent a lot of time with Python and really think that argparse as of 3.x makes most non-stdlib libraries for parsing are unnecessary. You get a ton of functionally and ability to add end-user documentation as you go, while abstracting away some of the basics like type casting/checking

The addition of Match-Case, while not adding much, functionally, does a LOT for readability and keeping logic clear.

[–] bloopernova 3 points 1 year ago (1 children)

I'm so annoyed with myself for using Traitlets for command line argument parsing! Your solution using argparse has so many more useful options, like the ability to define a mutually exclusive group of arguments.

Sigh. I live and learn and code a bunch more lol.

[–] [email protected] 2 points 1 year ago

No reason to be annoyed with yourself. It's part of the process of learning. In starting with Traitlets, you tried something new to you and between that and refactoring to use argparse, you've given yourself more practice writing code and learned a bit more about available libraries. And, at the same time, you've worked through the logic of your CLI design, building a better understanding of ways to organize arguments.

[–] [email protected] 11 points 1 year ago (2 children)

It's never pointless. Command-line arguments are often tricky to get right, especially when your program can do a lot of different things.

[–] [email protected] 6 points 1 year ago

Strings and human beings are two things that mix to create really tough problems.

[–] bloopernova 3 points 1 year ago (1 children)

I just wish I had thought things through correctly but I guess that's part of becoming a more experienced coder. Arrrrggh! I'm so annoyed I wasted time on the otherwise excellent Traitlets' argument parsing!

[–] [email protected] 4 points 1 year ago

I said it a billion times before but: failing is how you learn. You discover why you're wrong, and then you understand how to be right.

That's why I think juniors should not use ChatGPT but that's another story...

[–] [email protected] 9 points 1 year ago (1 children)

Python has a builtin argparse module. You should use that. It does all of the heavy lifting and logic for you.

[–] bloopernova 1 points 1 year ago

I was doing it wrong. I was trying to do appname thing --action when I should have been doing appname action --thing. Thank you for commenting, I'm sorry to waste your time.

[–] [email protected] 6 points 1 year ago (3 children)

What module are you currently using? I think the built-in module argparse should solve your problem but maybe I'm misunderstanding what you're asking for.

[–] bloopernova 2 points 1 year ago

I already have the arguments parsed, the bit that I'm confused about is where I decide what to do based on those various combinations of arguments.

I'm using Traitlets.

[–] bloopernova 2 points 1 year ago (1 children)

I was doing it wrong. I was trying to do appname thing --action when I should have been doing appname action --thing. Thank you for commenting, I'm sorry to waste your time.

[–] [email protected] 1 points 1 year ago

No worries, this ain't Stackoverflow ;-)

[–] [email protected] 2 points 1 year ago (1 children)

Others have already mentioned argparse, which is a pretty great module for argument parsing if you want to use something built-in. If you're willing to look into other libraries though, I'd recommend taking a look at Click. You should be able to pip install click to use it. It takes advantage of defining the CLI through decorators, and from my experience, works better when you want to have subcommands.

[–] bloopernova 2 points 1 year ago

Interesting! I'll check it out.

I've replaced the Traitlets based arg parsing with argparse, and the subcommands are working ok so far. I'll see if Click can add anything, thank you for the recommendation!

[–] [email protected] 1 points 1 year ago (1 children)

Seems like a for statement and a case statement could work.

Detect numargs

For a = 0 to numargs

Get core command on arg a (probably read until you hit an equals sign or a space), trim and lcase

Select Case core command

Case "thing"

Do stuff

Case "--name" Get part after equals and do stuff

Case "-i" Get next arg and increment a

End select

[–] bloopernova 2 points 1 year ago (1 children)

I was doing it wrong. I was trying to do appname thing --action when I should have been doing appname action --thing. Thank you for commenting, I'm sorry to waste your time.

[–] [email protected] 2 points 1 year ago

No worries, it was fun trying to figure out a solution to the problem regardless.