For many of my Python modules I find it convenient to provide a small if __name__ == "__main__": ...
block to be able to call individual methods from the command line. This requires some kind of translation of command-line string arguments to args
and kwargs
for the method call. This recipe uses a few conventions to do that:
- the first argument is the method name
- positional args are positional (duh)
- "key=value" is a keyword argument
- an attempt is made to interpret arguments as JSON to allow specifying types other than string
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | def _method_info_from_argv(argv=None):
"""Command-line -> method call arg processing.
- positional args:
a b -> method('a', 'b')
- intifying args:
a 123 -> method('a', 123)
- json loading args:
a '["pi", 3.14, null]' -> method('a', ['pi', 3.14, None])
- keyword args:
a foo=bar -> method('a', foo='bar')
- using more of the above
1234 'extras=["r2"]' -> method(1234, extras=["r2"])
@param argv {list} Command line arg list. Defaults to `sys.argv`.
@returns (<method-name>, <args>, <kwargs>)
"""
import json
import sys
if argv is None:
argv = sys.argv
method_name, arg_strs = argv[1], argv[2:]
args = []
kwargs = {}
for s in arg_strs:
if s.count('=') == 1:
key, value = s.split('=', 1)
else:
key, value = None, s
try:
value = json.loads(value)
except ValueError:
pass
if key:
kwargs[key] = value
else:
args.append(value)
return method_name, args, kwargs
|
Usage goes something like this:
if __name__ == "__main__":
import sys
method_name, args, kwargs = _method_info_from_argv(sys.argv)
rv = globals()[method_name](*args, **kwargs)
sys.exit(rv)
Not to diminish the work, but why over complicate?
can run from the cmd line:
R:\develop\nas\data\dev>python cmdline.py testFunction(1, 4, nice={4:'what', 'test': 5}, more=45, less=[1,2,3])
output:
1 4 less [1,2,3] more 45 nice {'test': 5, 4: 'what'}
Very nice. I had a further use case of needing to execute a file or command within a file remotely by passing a stringified json dictionary. It might be nicer to create a module that can be imported since i will probably be controlling all the python files, but in case I don't, I've written it as a file "cli.py" that can be copied along with the file to be executed. Called like this
python cli.py my_actual_file.py method_name --arg1 <arg1> --arg2 <arg2> ...
if method_name == "main" then call execfile passing in the appropriate sys.argv my_actual_file.py must accept the args either using argparse.parser.pare_args() in a main() function or as args to the "metnod_name"
With this, I can do things like
python cli.py copy_db_schema.py main --source_db "{'host': 'server1', 'port': 5432, 'user': 'me', 'pwd': '*'}" --dest_db ...similar...
or
python cli.py copy_db_schema.py list_tables --source_db "{'host': 'server1', 'port': 5432, 'user': 'me', 'pwd': '*'}"
where list_tables is a method accepting source_db dict params
Essentially, i'm wrapperizing your _method_info_from_argv
Listing
https://gist.github.com/a71648a8e90c09bf38c0.git