I’d trying to come up with something like a context manager that can branch based on input.
For example, say I am in Maya and I would like to early out if the selection is None. Normally I would do something like:
sel = cmds.ls(sl=True)
if not sel:
cmds.warning('Nothing Selected!')
else:
myFunc(sel)
If I wanted to personalize the message I would do something like this:
def myFunc(sel):
if not sel:
cmds.warning('myFunc requires at least 1 object selected.')
return
# stuff and things.
I’d like some kind of wrapper that could execute a block of code only if the input passes a test.
Maybe something like:
with somethingSelected(cmds.ls(sl=True)) as sel:
myFunc(sel)
where myFunc would not be executed if the current selection is None(it would also handle printing the warning).
I tried this with a context manager, but they always run the code they wrap. After reading up on context managers, they aren’t designed to handle conditions.
I came across the conditional package, which seems like it is designed specifically to address the same problem, but I’d like to use a home-grown solution.
Any ideas? Maybe a decorator with it’s own arguments would be a better choice?
A decorator is the more natural framework here, since the ‘context’ functionality isn’t really what you’re asking for. You could easily do a decorator that enforces some kinds of inputs and raises or aborts if it does not get what it wants – that’s a nice way of centralizing behavior. Here’s one I use that converts pynodes to strings for use with functions that don’t want pymel:
def accept_pynode(func):
'''
Decorator converts input arguments from pm to cmds if needed
'''
@wraps(func)
def py_wrapped(*args, **kwargs):
args = (i.fullPath() if hasattr(i, 'fullPath') else str(i) for i in args)
return func(*args, **kwargs)
return py_wrapped
It’s a lot easier than doing the conversion inside a zillion different functions. You could just add a line after the ‘args’ line to raise or print and bail (raising is better, IMHO, but it’s a style thing):
def accept_pynode(func):
'''
Decorator converts input arguments from pm to cmds if needed
'''
@wraps(func)
def py_wrapped(*args, **kwargs):
args = (i.fullPath() if hasattr(i, 'fullPath') else str(i) for i in args)
if len(args) < 1:
raise "no valid objects"
return func(*args, **kwargs)
return py_wrapped
If you want custom messages, you can grab the doc string of the wrapped function and scrape the message from there, or use a decorator class which takes the message as an argument
thanks for the tips! Quick question - what is this line doing?
@wraps(func)
edit: nevermind, found this very informative answer on SO.
One more question, let’s say I am not going to go the route of raising an exception is the selection is None, or empty. I Just want to print a warning and exit silently.
what would I set the return Value to in the decorator? A function that does nothing? None type?
In that case, you would have to write that inner function to early exit with the warning. In my example:
def accept_pynode(func):
'''
Decorator converts input arguments from pm to cmds if needed
'''
@wraps(func)
def py_wrapped(*args, **kwargs):
args = (i.fullPath() if hasattr(i, 'fullPath') else str(i) for i in args)
if len(args) < 1:
cmds.warning ("no valid objects")
return []
return func(*args, **kwargs)
return py_wrapped
the ‘right’ return is a matter of taste. I usually use none when the function returns a single value, but empty list when the function returns a list. That way callers can loop without special casing.