Motivation

Replicability—the ability for someone else to do exactly what you did at some point in the past, on the same or an equivalent machine, using the same tooling you used, and achieve the same results—is a complex problem. When someone has a problem running a script, there’s often a good deal of time wasted iterating back and forth between the person having the problem and those who might be able to help them solve it. Questions such as the following arise:

  • What kind of machine are you using?

  • What’s your environment look like?

  • What did you run at the command line?

  • Can you provide all the output?

And in our asynchronous work environment, there’s often quite a bit of time spent waiting for the person on the other end of the conversation to come back to it, think about a response, and then move the conversation forward. Frustration can build throughout, especially when you run into, “I just ran the same thing you did, and it worked fine for me.”

While tackling the issue of rock-solid replicability is far beyond the scope of a single package, reverse_argparse aims to make life a little bit easier when it comes to this last issue.

What Exactly Did You Run?

When your Python script requires input from the user, the go-to solution is the argparse module in the standard library, which makes it easy to write user-friendly command-line interfaces. As scripts grow in complexity, it’s common for the number of command-line arguments to grow, but then, for simplicity’s sake, it’s recommended to use default values wisely such that the user isn’t required to specify dozens of arguments each time they run the script.

In such a scenario, asking someone what they ran in the terminal may be insufficient, because

python3 my_script.py --foo bar

for one person, in one environment, in one location, on one machine, may be different from the same line executed by a different person, in a different environment, in a different location, on a different machine.

What Might Go Wrong?

The differences that may occur generally fall into three categories.

When default values change

Say you’re using one version of the script, and your collaborator is using a different one (perhaps they’re working on another branch). In your version

python3 my_script.py --foo bar

translates to

python3 my_script.py --foo bar --baz

but in their version it translates to

python3 my_script.py --foo bar --no-baz
#                                ^^^

because someone updated a default value. You’ll probably spend a good deal of time playing around with the debugger or print statements before you figure out the switch.

When using relative references

Changes in default values hopefully occur infrequently. Perhaps a more common occurrence is when using relative references. Say a script requires some file as input, so you run

python3 my_script.py --foo bar.txt

and this translates to

python3 my_script.py --foo /path/to/some-dir/bar.txt

However, your colleague, not having all the information needed to replicate your error, runs the exact same thing, but it translates to

python3 my_script.py --foo /path/to/some-other-dir/bar.txt
#                                   ^^^^^^^^^^^^^^

There’s no telling how long you two will go back and forth before realizing that your relative references resolved to different files.

When arguments are post-processed

A final potential source of confusion is when arguments go through some amount of post-processing after they’re read in via argparse.ArgumentParser.parse_args(). For instance, perhaps the input is normalized in some way, as in the case where

python3 my_script.py --start-time '30 minutes ago'

translates to

python3 my_script.py --start-time '2023-07-01T12:34:56Z'

but when your colleague gets around to running your script a few days later, the exact same line translates to

python3 my_script.py --start-time '2023-07-05T05:43:21Z'

Who knows how long it’ll take you two to figure out you’re actually looking at different data sets under the hood?

What’s the Solution?

The reverse_argparse module aims to solve these problems for you. It takes as input two things:

  1. The argparse.Namespace of parsed arguments, which already accounts for any default values applied, and any post-processing you might’ve done with them.

  2. The argparse.ArgumentParser that parsed the arguments.

These two are then used to undo the parsing such that you can generate a string of the complete, effective command line invocation of the script. When someone asks what you ran in the terminal, you can give them this string and avoid a good deal of wasted time, confusion, and frustration, which will hopefully help you solve your problems more quickly.