ResultsMotivated.com

Parse Python literals from command line arguments using argparse

<2024-04-11>

Here's a unique Python hack (that might be a bad idea.)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on 2024-04-11T18:32:02-04:00

@author: nate
"""
import argparse
import ast
import os

basedir = os.path.dirname(__file__)

class PyLiteralAction(argparse.Action):
    '''

    Parse 0 or more Python literals as Python objects.

    Example:

    parser = argparse.ArgumentParser(description="Argparse demo")
    parser.add_argument('--test-arg', action=PyLiteralAction, help="A test action")
    args = parser.parse_args()
    '''
    def __init__(self,
                 option_strings,
                 dest,
                 default=None,
                 required=True,
                 help=None):

        super().__init__(option_strings=option_strings,
                         dest=dest,
                         nargs="*",
                         required=required,
                         help=help)

    def __call__(self, parser, namespace, values, option_string=None):
        parsed_obj = True
        if len(values) == 0:
            setattr(namespace, self.dest, None)
            return

        values = ",".join(values)
        parsed_obj = ast.literal_eval(values)
        setattr(namespace, self.dest, parsed_obj)


parser = argparse.ArgumentParser(description="Argparse demo")
parser.add_argument('--test-arg', action=PyLiteralAction, help="A test action")
args = parser.parse_args()

obj_type = type(args.test_arg)
print(f"{obj_type}: {args.test_arg}")

The idea here is to make use of Python's built-in ast module to parse command line arguments as "simple" Python objects.

1. Example

Let us take this opportunity to interactively explore the properties of this argparse action. For one, it's required; failing to include it produces an error:

./literal_action.py
usage: literal_action.py [-h] --arg1 [ARG1 ...]
literal_action.py: error: the following arguments are required: --arg1

If we include --arg1 but do not provide any value,

./literal_action.py
usage: literal_action.py [-h] --arg1 [ARG1 ...]
literal_action.py: error: the following arguments are required: --arg1

We can use this to pass simple Python literals such as integers:

./literal_action.py --arg1=44444
44444 (type: <class 'int'>)

Or a tuple of literals,

./literal_action.py --arg1="(1, 2, 3, '4')"
(1, 2, 3, '4') (type: <class 'tuple'>)

Or a dictionary of literals,

./literal_action.py --arg1 "{1: 'A', 2: 'B'}"
{1: 'A', 2: 'B'} (type: <class 'dict'>)

Another interesting property is that multiple arguments separated by a space are parsed into a tuple.

./literal_action.py --arg1 1 2 3
(1, 2, 3) (type: <class 'tuple'>)

It can also parse complicated tuples of literals like this one:

./literal_action.py --arg1 "{1: 'A', 2: 'B'}" 123 456
({1: 'A', 2: 'B'}, 123, 456) (type: <class 'tuple'>)

Modified: 2024-09-10 10:10:00 EDT

Emacs 29.1.50 (Org mode 9.7.6)