Python cmd module - parsing values from line - python

I am working on a quick python script using the cmd module that will allow the user to enter text commands followed by parameters in basic url query string format. The prompts will be answered with something like
commandname foo=bar&baz=brack
Using cmd, I can't seem to find which method to override to affect the way the argument line is handed off to all the do_* methods. I want to run urlparse.parse_qs on these values, and calling this upon line in every do_* method seems clumsy.
The precmd method gets the whole line, before the commandname is split off and interpreted, so this will not work for my purposes. I'm also not terribly familiar with how to place a decorator inside a class like this and haven't been able to pull it off without breaking the scope.
Basically, the python docs for cmd say the following
Repeatedly issue a prompt, accept input, parse an initial prefix off
the received input, and dispatch to action methods, passing them the
remainder of the line as argument.
I want to make a method that will do additional processing to that "remainder of the line" and hand that generated dictionary off to the member functions as the line argument, rather than interpreting them in every function.
Thanks!

You could potentially override the onecmd() method, as the following quick example shows. The onecmd() method there is basically a copy of the one from the original cmd.py, but adds a call to urlparse.parse_qs() before passing the arguments to a function.
import cmd
import urlparse
class myCmd(cmd.Cmd):
def onecmd(self, line):
"""Mostly ripped from Python's cmd.py"""
cmd, arg, line = self.parseline(line)
arg = urlparse.parse_qs(arg) # <- added line
if not line:
return self.emptyline()
if cmd is None:
return self.default(line)
self.lastcmd = line
if cmd == '':
return self.default(line)
else:
try:
func = getattr(self, 'do_' + cmd)
except AttributeError:
return self.default(line)
return func(arg)
def do_foo(self, arg)
print arg
my_cmd = myCmd()
my_cmd.cmdloop()
Sample output:
(Cmd) foo
{}
(Cmd) foo a b c
{}
(Cmd) foo a=b&c=d
{'a': ['b'], 'c': ['d']}
Is this what you are trying to achieve?
Here's another potential solution that uses a class decorator to modify a
cmd.Cmd subclass and basically apply a decorator function to all do_*
methods of that class:
import cmd
import urlparse
import types
# function decorator to add parse_qs to individual functions
def parse_qs_f(f):
def f2(self, arg):
return f(self, urlparse.parse_qs(arg))
return f2
# class decorator to iterate over all attributes of a class and apply
# the parse_qs_f decorator to all do_* methods
def parse_qs(cls):
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if attr_name.startswith('do_') and type(attr) == types.MethodType:
setattr(cls, attr_name, parse_qs_f(attr))
return cls
#parse_qs
class myCmd(cmd.Cmd):
def do_foo(self, args):
print args
my_cmd = myCmd()
my_cmd.cmdloop()
I quickly cobbled this together and it appears to work as intended, however, I'm
open to suggestions on any pitfalls or how this solution could be improved.

Related

Return a function object with wrapper

I have a complex library, which users can add functions to. This library is then used in a program which then accepts input. Problem is, the functions aren't being processed the way I want them to. Let me illustrate (I've simplified the code to illustrate the salient points):
import functools
def register_command(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
def execute_command(func, *args, **kwargs)):
return functools.partial(func, *args, **kwargs))
'''DECORATORS: EASY WAY FOR THE USER TO ADD NEW FUNCTIONS TO THE LIBRARY'''
#register_command
def some_math_function(variable):
return variable + 1
'''THIS IS THE PROGRAM WHICH USES THE ABOVE LIBRARY & USER-CREATED FUNCTIONS'''
input_var = input("input your variable: ")
response = execute_command(register_command, input_var)
print(response())
So, if the user were to input '10', input_var = 10, I expect the algorithm to return 11. Instead I get this:
<function register_command.<locals>.wrapper at 0x110368ea0>
Where am I going wrong?
Let's look at what decorators do first. A decorator accepts an object (could be a function, class, or possibly something else), does something, and returns a replacement object that gets reassigned to the name of the original.
In that context, register_function does nothing useful. It returns a wrapper that calls the unmodified original. Based on the name, you'd probably want to record the function in some registry, like a dictionary:
function_registry = {}
def register_command(func):
function_registry[func.__name__] = func
return func
Remember, the things that a decorator does don't have to affect the function directly (or at all). And there's nothing wrong with simply returning the input function.
Now let's take a look at using the registry we just made. Your current execute_command does not execute anything. It creates and returns a callable object which would call the decorated function if you were to invoke it with no arguments. execute_command does not actually call the result.
You would probably want a function with a name like execute_command to look up a command by name, and run it:
def execute_command(name, *args, **kwargs)):
return function_registry[name](*args, **kwargs)
So now you can do something like
#register_command
def some_math_function(variable):
return variable + 1
That will leave the function unmodified, but add an entry to the registry that maps the name 'some_math_function' to the function object some_math_function.
Your program becomes
func_name = input('what function do you want to run? ') # enter some_math_func
input_var = int(input("input your variable: "))
response = execute_command(func_name, input_var)
print(response())
This example does not perform any type checking, keep track of the number of arguments, or otherwise allow you to get creative with calling the functions, but it will get you started with decorators.
So here's a rudimentary system for converting a fixed number of inputs to the required types. Arguments are converted in the order provided. The function is responsible for raising an error if you didn't supply enough inputs. Keywords aren't supported:
function_registry = {}
def register_function(*converters):
def decorator(func):
def wrapper(*args):
real_args = (c(arg) for c, arg in zip(converters, args))
return func(*real_args)
function_registry[func.__name__] = wrapper
return wrapper
return decorator
#registrer_function(int)
def some_math_func(variable):
return variable + 1
def execute_command(name, *args):
return function_registry[name](*args)
command = input("command me: ") # some_math_func 1
name, *args = command.split()
result = execute_command(name, *args)
print(result)

Pass instance name as argument from several UI controls to a single function in Python-Maya

I'm developing a UI in python for maya, and I'm trying to do a function that performs an action when a frameLayout expands, in this case I have several "frameLayout" objects and I would like to use a single function "fl_expand", instead of one for each object
def fl_expand(*args):
print args
with frameLayout("layout1", collapsable=True, expandCommand=fl_expand):
...
with frameLayout("layout2", collapsable=True, expandCommand=fl_expand):
...
but I don't know how to pass the instance name as argument to the function, I tried:
with frameLayout("layout1", collapsable=True, expandCommand=fl_expand("layout1")):
...
But of course I get this error:
# Error: TypeError: file /usr/autodesk/maya2018/lib/python2.7/site-packages/pymel/internal/pmcmds.py line 134: Invalid arguments for flag 'ec'. Expected string or function, got NoneType #
Currently, you have something like that:
def fl_expand(*args):
print(args)
def frameLayout(name, collapsable, expandCommand):
print("frameLayout: " + name)
expandCommand('foo')
frameLayout("layout1", collapsable=True, expandCommand=fl_expand)
What you want is to call the fl_expand function with the first argument already filled with the name of the layout. To do that, you can use a partiel function. See the documentation for functools.partial.
You can try:
import functools
frameLayout("layout1", collapsable=True, expandCommand=functools.partial(fl_expand, "layout1"))
Of course, it could be laborious if you have a lot of calls like that. You can also define your own frameLayout function:
def myFrameLayout(name, collapsable, expandCommand):
cmd = functools.partial(fl_expand, name)
return frameLayout(name, collapsable, cmd)
myFrameLayout("layout2", collapsable=True, expandCommand=fl_expand)

How can I collect unspecified command line options when using Python's `argh`?

With the Python CLI library argh I want to write a wrapper tool. This wrapper tool is suppose to read the two options -a and -b and to pass all other options to a function (which then calls the wrapped UNIX tool with the left-over options via subprocess).
I have experimented with dispatch's parameter skip_unknown_args:
def wrapper(a=True, b=False):
print("Enter wrapper")
# 1. process a and b
# 2. call_unix_tool(left-over-args)
if __name__ == '__main__':
parser = argh.ArghParser()
argh.set_default_command(parser, wrapper)
argh.dispatch(parser, skip_unknown_args=True)
However the program still does exit when it encounters unknown options and it does not enter the function wrapper as needed. Additionally I don't know where the unknown/skipped arguments are stored, so that I can pass them to the UNIX tool.
How can I tell argh to go into wrapper with the skipped arguments?
I believe this is a bug.
when skip_unknown_args=True, here namespace_obj is a tuple, with a namespace object and remaining args:
(Pdb) p namespace_obj
(ArghNamespace(_functions_stack=[<function wrapper at 0x105cb5e18>], a=False, b=True), ['-c'])
underlying _get_function_from_namespace_obj expects an unary one:
154 function = _get_function_from_namespace_obj(namespace_obj)
...
191 if isinstance(namespace_obj, ArghNamespace):
I checked its coressponding issue and unittest, no idea what is the legitmate behivour the author expects, have dropped a comment there as well.
why not use argparse directly?
You cannot do this with skip_unknown_args=True, because as #georgexsh pointed out the argh library doesn't seem to behave sensibly with that option. However you can provide your own parser class which injects the unknown arguments into the normal namespace:
class ArghParserWithUnknownArgs(argh.ArghParser):
def parse_args(self, args=None, namespace=None):
namespace = namespace or ArghNamespace()
(namespace_obj, unknown_args) = super(ArghParserWithUnknownArgs, self).parse_known_args(args=args, namespace=namespace)
namespace_obj.__dict__['unknown_args'] = unknown_args
return namespace_obj
Note that this class' parse_args method calls ArgParser's parse_known_args method!
With this class defined you can write the wrapper code following way:
def wrapper(a=True, b=False, unknown_args={}):
print("a = %s, b = %s" % (a,b))
print("unknown_args = %s" % unknown_args)
if __name__ == '__main__':
parser = ArghParserWithUnknownArgs()
argh.set_default_command(parser, wrapper)
argh.dispatch(parser)
In your main function wrapper you can access all unknown arguments via the parameter unknown_args and pass this on to your subprocess command
ps: In order to keep the help message tidy decorate wrapper with
#argh.arg('--unknown_args', help=argparse.SUPPRESS)
Addendum: I created an enhanced version of the parser and compiled it into a ready-to-use module. Find it on Github.

How to pass a string as an object to getattr python

I have a number of functions that need to get called from various imported files.
The functions are formated along the lines of this:
a.foo
b.foo2
a.bar.foo4
a.c.d.foo5
and they are passed in to my script as a raw string.
I'm looking for a clean way to run these, with arguments, and get the return values
Right now I have a messy system of splitting the strings then feeding them to the right getattr call but this feels kind of clumsy and is very un-scalable. Is there a way I can just pass the object portion of getattr as a string? Or some other way of doing this?
import a, b, a.bar, a.c.d
if "." in raw_script:
split_script = raw_script.split(".")
if 'a' in raw_script:
if 'a.bar' in raw_script:
out = getattr(a.bar, split_script[-1])(args)
if 'a.c.d' in raw_script:
out = getattr(a.c.d, split_script[-1])(args)
else:
out = getattr(a, split_script[-1])(args)
elif 'b' in raw_script:
out = getattr(b, split_script[-1])(args)
It's hard to tell from your question, but it sounds like you have a command line tool you run as my-tool <function> [options]. You could use importlib like this, avoiding most of the getattr calls:
import importlib
def run_function(name, args):
module, function = name.rsplit('.', 1)
module = importlib.import_module(module)
function = getattr(module, function)
function(*args)
if __name__ == '__main__':
# Elided: retrieve function name and args from command line
run_function(name, args)
Try this:
def lookup(path):
obj = globals()
for element in path.split('.'):
try:
obj = obj[element]
except KeyError:
obj = getattr(obj, element)
return obj
Note that this will handle a path starting with ANY global name, not just your a and b imported modules. If there are any possible concerns with untrusted input being provided to the function, you should start with a dict containing the allowed starting points, not the entire globals dict.

Aliases for commands with Python cmd module

How can I create an alias for a command in a line-oriented command interpreter implemented using the cmd module?
To create a command, I must implement the do_cmd method. But I have commands with long names (like constraint) and I want to provide aliases (in fact, shortcuts) for these commands (like co). How can I do that?
One possibility that came to my mind is to implement the do_alias (like do_co) method and just calling do_cmd (do_constraint) in this method. But this brings me new commands in the help of the CLI.
Is there any other way to achieve this? Or may be is there a way to hide commands from the help output?
The following solution makes aliased commands share a single help message. It lets you keep all of your aliases in a single easy to edit place, while making documentation much easier. It checks user input against an alias dictionary with function values and overrides both the default() (See sloth & brian) and do_help() methods.
Here I've made aliases 'c' and 'con' execute do_constraint(), 'q' invoke do_quit(), and 'h' invoke do_help(). The bonus of this solution is that 'h q' and 'help quit' print the same message. Documentation for several aliased commands can maintained in a single docstring.
import cmd
class prompt(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.aliases = { 'c' : self.do_constraint ,
'con' : self.do_constraint ,
'q' : self.do_quit ,
'h' : self.do_help }
def do_constraint(self, arg):
'''Constrain things.'''
print('Constraint complete.')
def do_quit(self, arg):
'''Exit the program.'''
return True
def do_help(self, arg):
'''List available commands.'''
if arg in self.aliases:
arg = self.aliases[arg].__name__[3:]
cmd.Cmd.do_help(self, arg)
def default(self, line):
cmd, arg, line = self.parseline(line)
if cmd in self.aliases:
self.aliases[cmd](arg)
else:
print("*** Unknown syntax: %s" % line)
You can overwrite the default method and search for a suitable command handler (as already suggested by Brian):
import cmd
class WithAliasCmd(cmd.Cmd):
def default(self, line):
cmd, arg, line = self.parseline(line)
func = [getattr(self, n) for n in self.get_names() if n.startswith('do_' + cmd)]
if func: # maybe check if exactly one or more elements, and tell the user
func[0](arg)
The docs mention a default method, which you can override to handle any unknown command. Code it to prefix scan a list of commands and invoke them as you suggest for do_alias.

Categories

Resources