Dynamically access instance method [duplicate] - python

This question already has answers here:
How to dynamically select a method call?
(5 answers)
Closed 9 years ago.
So I have a little script in python and for the help I want to print each method docstring. For example
~$ myscript.py help update
would print myClass.update.__doc__ to the screen. The code I was trying to run is this:
import sys
class myClass:
def update(self):
""" update method help """
def help(self):
method = sys.argv[2:3][0]
if method == "update":
print "Help: " + self.update.__doc__
myClass = myClass()
myClass.help()
It works, but as my methods collection grows it will be a pain in the ass to make the help work as intend. Is there anyway to call something like self.method.__doc__ dynamically? Thanks.

Instead of using this:
if method == 'update':
help_string = self.update.__doc__
you could use more flexible solution:
help_string = getattr(self, method).__doc__
Just make sure that you catch AttributeErrors (it will be thrown when there is no method with given name).

This will do it:
method = sys.argv[2:3][0] # This is a bit odd; why not sys.argv[2]?
print "Help: " + getattr(self, method).__doc__

I would use argparse for this:
import argparse
import inspect
class myClass(object):
"""description for program"""
def update(self):
"""update method help"""
print 'update command'
def something(self):
"""something command help"""
print 'something command'
if __name__ == '__main__':
program = myClass()
parser = argparse.ArgumentParser(description=program.__doc__)
subparsers = parser.add_subparsers()
for name, method in inspect.getmembers(program, predicate=inspect.ismethod):
subparser = subparsers.add_parser(name, help=method.__doc__)
subparser.set_defaults(method=method)
args = parser.parse_args()
args.method()
Example on the command line:
$ python ~/test/docargparse.py --help
usage: docargparse.py [-h] {something,update} ...
description for program
positional arguments:
{something,update}
something something command help
update update method help
optional arguments:
-h, --help show this help message and exit
$ python ~/test/docargparse.py
usage: docargparse.py [-h] {something,update} ...
docargparse.py: error: too few arguments
$ python ~/test/docargparse.py update
update command
$ python ~/test/docargparse.py something
something command

Something like
import inspect
class T:
def test(self):
'''test'''
pass
for t in inspect.getmembers(T, predicate=inspect.ismethod):
print t[1].__doc__
should scale pretty well.

Related

Display full help text in python click

I am having the following problem and I am fearful there isn't a straghtforward way to solve it so I am asking here. I am using Click to implement a CLI and I have created several grouped commands under the main command. This is the code:
#click.group()
def main():
pass
#main.command()
def getq():
'''Parameters: --questionnaire_id, --question_id, --session_id, --option_id'''
click.echo('Question Answers')
When I type the main command alone in my terminal it lists all the subcommands with the help text next to each one. However, the text is not displayed fully for the case of getq. Instead, it displays only "Parameters: --questionnaire_id, --question_id,... ."
Is there a way to display it all?
Thank You
The easiest way to do this is to use the command's short_help argument:
#click.group()
def main():
pass
#main.command(short_help='Parameters: --questionnaire_id, --question_id, --session_id, --option_id')
def getq():
click.echo('Question Answers')
If you insist to use the docstring for this and want to override the automatic shortening of it, then you could use a custom Group class overriding the format_commands method to directly use cmd.help instead of the get_short_help_str method:
import click
from gettext import gettext as _
class FullHelpGroup(click.Group):
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
"""Extra format methods for multi methods that adds all the commands
after the options.
"""
commands = []
for subcommand in self.list_commands(ctx):
cmd = self.get_command(ctx, subcommand)
# What is this, the tool lied about a command. Ignore it
if cmd is None:
continue
if cmd.hidden:
continue
commands.append((subcommand, cmd))
# allow for 3 times the default spacing
if len(commands):
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
rows = []
for subcommand, cmd in commands:
help = cmd.help if cmd.help is not None else ""
rows.append((subcommand, help))
if rows:
with formatter.section(_("Commands")):
formatter.write_dl(rows)
#click.group(cls=FullHelpGroup)
def main():
pass
#main.command()
def getq():
'''Parameters: --questionnaire_id, --question_id, --session_id, --option_id'''
click.echo('Question Answers')
if __name__ == "__main__":
main()
You most probably want to override the max_content_width (at most 80 columns by default) also. You could do this by overriding the context settings:
import shutil
#click.group(cls=FullHelpGroup,
context_settings={'max_content_width': shutil.get_terminal_size().columns - 10})
def main():
pass

Call python function based on command line argument

I have a script with several functions:
def a():
pass
def b():
pass
def c():
pass
Which by design will be invoked depending on cmd line argument. I can create several if statements which will evaluate which function should run:
if args.function == "a":
a()
elif args.function == "b":
b()
elif args.function == "c":
c()
But is there a better way to do this?
You could make a dictionary like so
d = {"a" : a,
"b" : b}
and then dispatch
d[args.function]()
Perhaps you are looking for a library like click? It lets you easily add command-line subcommands with a decorator.
import click
#click.group()
def cli():
pass
#cli.command()
def a():
print("I am a")
#cli.command()
def b():
print("Je suis b")
if __name__ == '__main__':
cli()
Sample output:
bash$ ./ick.py --help
Usage: ick.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
a
b
bash$ ./ick.py a
I am a
Try using eval
eval(function_name_passed_as_argument + "()")
def a():
pass
def b():
pass
eval(args.function + "()")
This doesn't require the use of if-else logic. Function name passed as argument will be executed directly.
You make a dictionary as already pointed out, but how are you going to handle a bad input? I would create a default method and use the dict.get method:
def fallback():
print('That command does not exist')
# add any code you want to be run for
# a bad input here...
functions = {
'a': a,
'b': b
}
Then call the function by retrieving it:
functions.get(args.function.lower(), fallback)()
Python has several built-in functions that we can utilize for instance Argparse, this method pretty common in python for command line programmable application.
The basics:
import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
By this method, you can have something like this:
$ python3 prog.py -v
verbosity turned on
$ python3 prog.py --help
usage: prog.py [-h] [-v]
optional arguments:
-h, --help show this help message and exit
-v, --verbose increase output verbosity

Use embedded argparse in Python3

I want to make a Python module that can be used both by command line and other modules.
Like that :
python3 Capacity.py arg1 arg2 arg3
or
>>> capacity.execByString("arg1 arg2 arg3")
I made a class to (with some researches) get the result of argparse within the code :
class ArgumentParserError(Exception): pass
class Parseur(ArgumentParser):
def error(self, msg):
raise ArgumentParserError(msg)
def analyze(self, args):
if type(args) is not list:
args = args.split() # To work with a String
try:
result = self.parse_args(args)
return True, result
# Returns True and the namespace if OK
except ArgumentParserError as err:
return False, err.args[0]
# Returns False and the error message if not OK
I use it like this :
class Capacity():
def __init__(self):
self.parser = Parseur()
# Config the parser
def execByArguments(*args):
# Do the job
def execByString(command):
isOK, result = self.parser.analyze(command)
if isOk:
# Launch execByArguments with the rights args in result
else:
# Print error message
print(result)
def execFromCommandLine():
args = self.parser.parse_args()
# Launch execByArguments with the rights args
if __name__ == "__main__":
execFromCommandLine()
But there's 2 main problems and surely some I have'nt yet discovered :
the args are not parsed correctly (doubles quotes for example) as the split function has the "spaces" separator
using the -h flag close the program anyway
I'm convinced that making this another Parseur class is useless/not good and there's a workaround.
Launching the module via subprocess is not a good idea neither : I want to get the returned object in that case.
Can you help me to find a cool way to do what i want please ?
Thanks already.
PS : Write code on the online formular is such a pain ^^.
You are not far away. I would do it somehow like this:
class Capacity():
def __init__(self, argv):
# take over and store arguments (or process further parsing)
self.parser = Parseur()
isOk, result = self.parser.analyze(argv)
def argInputValidation(argv):
#checking the command line arguments given by user
#and returning valid argv, otherwise exit program
#with an error message.
return argv
if __name__ == "__main__":
obj = Capacity(argInputValidation(sys.argv[1:]))

Save a command line option's value in an object with Python's Click library

I want to parse some command line arguments with Python's Click library and save the provided values in an object.
My first guess would be to do it like this:
import click
class Configuration(object):
def __init__(self):
# configuration variables
self.MyOption = None
# method call
self.parseCommandlineArguments()
#click.command()
#click.option('--myoption', type=click.INT, default=5)
def parseCommandlineArguments(self, myoption):
# save option's value in the object
self.MyOption = myoption
# create an instance
configuration = Configuration()
print(configuration.MyOption)
However, this does not work, instead I get:
TypeError: parseCommandlineArguments() takes exactly 2 arguments (1 given)
Apparently, passing self to the decorated function is not the correct way to do it. If I remove self from the method arguments then I can e.g. do print(myoption) and it will print 5 on the screen but the value will not be known to any instances of my Configuration() class.
What is the correct way to handle this? I assume it has something to do with context handling in Click but I cannot get it working based on the provided examples.
If I'm understanding you correctly, you want a command line tool that will take configuration options and then do something with those options. If this is your objective then have a look at the example I posted. This example uses command groups and passes a context object through each command. Click has awesome documentation, be sure to read it.
import click
import json
class Configuration(object):
"""
Having a custom context class is usually not needed.
See the complex application documentation:
http://click.pocoo.org/5/complex/
"""
my_option = None
number = None
is_awesome = False
uber_var = 900
def make_conf(self):
self.uber_var = self.my_option * self.number
pass_context = click.make_pass_decorator(Configuration, ensure=True)
#click.group(chain=True)
#click.option('-m', '--myoption', type=click.INT, default=5)
#click.option('-n', '--number', type=click.INT, default=0)
#click.option('-a', '--is-awesome', is_flag=True)
#pass_context
def cli(ctx, myoption, number, is_awesome):
"""
this is where I will save the configuration
and do whatever processing that is required
"""
ctx.my_option = myoption
ctx.number = number
ctx.is_awesome = is_awesome
ctx.make_conf()
pass
#click.command('save')
#click.argument('output', type=click.File('wb'))
#pass_context
def save(ctx, output):
"""save the configuration to a file"""
json.dump(ctx.__dict__, output, indent=4, sort_keys=True)
return click.secho('configuration saved', fg='green')
#click.command('show')
#pass_context
def show(ctx):
"""print the configuration to stdout"""
return click.echo(json.dumps(ctx.__dict__, indent=4, sort_keys=True))
cli.add_command(save)
cli.add_command(show)
After this is installed your can run commands like this:
mycli -m 30 -n 40 -a show
mycli -m 30 -n 40 -a save foo.json
mycli -m 30 -n 40 -a show save foo.json
The complex example is an excellent demo for developing a highly configurable multi chaining command line tool.

Access function from within scripts and from commandline

I want to do the following:
I have a class which should provide several functions, which need different inputs. And I would like to use these functions from within other scripts, or solely from commandline.
e.g. I have the class "test". It has a function "quicktest" (which basically justs prints something). (From commandline) I want to be able to
$ python test.py quicktest "foo" "bar"
Whereas quicktest is the name of the function, and "foo" and "bar" are the variables.
Also (from within another script) I want to
from test import test
# this
t = test()
t.quicktest(["foo1", "bar1"])
# or this
test().quicktest(["foo2", "bar2"])
I just can't bring that to work. I managed to write a class for the first request and one for the second, but not for both of them. The problem is that I sometimes have to call the functions via (self), sometimes not, and also I have to provide the given parameters at any time, which is also kinda complicated.
So, does anybody have an idea for that?
This is what I already have:
Works only from commandline:
class test:
def quicktest(params):
pprint(params)
if (__name__ == '__main__'):
if (sys.argv[1] == "quicktest"):
quicktest(sys.argv)
else:
print "Wrong call."
Works only from within other scripts:
class test:
_params = sys.argv
def quicktest(self, params):
pprint(params)
pprint(self._params)
if (__name__ == '__main__'):
if (sys.argv[1] == "quicktest"):
quicktest()
else:
print "Wrong call"
try the following (note that the different indentation, the if __name__ part is not part of class test anymore):
class test:
def quicktest(params):
pprint(params)
if __name__ == '__main__':
if sys.argv[1] == "quicktest":
testObj = test()
testObj.quicktest(sys.argv)
else:
print "Wrong call."
from other scripts:
from test import test
testObj = test()
testObj.quicktest(...)
The if __name__ == '__main__': block needs to be at the top level:
class Test(object): # Python class names are capitalized and should inherit from object
def __init__(self, *args):
# parse args here so you can import and call with options too
self.args = args
def quicktest(self):
return 'ret_value'
if __name__ == '__main__':
test = Test(sys.argv[1:])
You can parse the command line with the help of argparse to parse the value from the command line.
Your class which has the method and associate methods to arguments.

Categories