Adding options which both change behaviour and store an argument - python

Using the argparse module, is it possible to perform multiple actions for a given argument?
Specifically, I'd like to provide a -l/--list option with nargs='?' that will change the behaviour of the program from its main function to one of giving information about all possibilities in a set or about one particular possibility.
Normally there will be a namespace attribute that contains a function to be called after parsing; I would like the -l option to both change this attribute and optionally store its argument in a different attribute.
Is this possible?

Simply implement your own Action subclass. This basically looks like this:
class ListAction(argparse.Action):
def __call__(parser, namespace, values, option_string=None):
setattr(namespace, 'list', values[0])
do_something_completely_different()
The argparse documentation has more details.

Related

Argparse append action with default value only if argument doesn't appear

I'm parsing CLI arguments in my program with the argparse library. I would like to parse an argument that can repeat, with the following behaviour:
if the argument appears at least once, its values are stored in a list,
if the argument doesn't appear, the value is some default list.
I have the following code so far:
import argparse
ap = argparse.ArgumentParser(description="Change channel colours.")
ap.add_argument('-c', '--channel', action='append', default=['avx', 'fbx'])
print(ap.parse_known_args(['-c', 'iasdf', '-c', 'fdas']))
print(ap.parse_known_args())
This appropriately sets a default list, however it doesn't start with an empty list when the argument appears. In other words, the second print statement prints the correct value (the default list), but the first one prints
['avx', 'fbx', 'iasdf', 'fdas']
instead of
['iasdf', 'fdas']
Is there a way in argparse to do what I want without doing something like
if len(args.channel) > 2:
args.channel = args.channel[2:]
after the fact?
There's a bug/issue discussing this behavior. I wrote several posts to that.
https://bugs.python.org/issue16399 argparse: append action with default list adds to list instead of overriding
For now the only change is in documentation, not in behavior.
All defaults are placed in the namespace at the start of parsing. For ordinary actions, user values overwrite the default. But in the append case, they are just added to what's there already. It doesn't try to distinguish between values placed by the default, and previous user values.
I think the simplest solution is to leave the default as is, and check after parsing for None or empty list (I don't recall which), and insert your default. You don't get extra points for doing all the parsing in argparse. A bit of post parsing processing is quite ok.

check if argparser already has arg defined

How can I detect if an argument already exists in an ArgumentParser? For example, I'd like to do something like this:
import argparse
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("--arg1")
if not has_arg(arg_parser, "--arg1"):
arg_parser.add_argument("--arg1")
In this example it seems pointless, but I have a use case where has_arg would be useful. I have utility functions that take an arg_parser and add a bunch of arguments to it. These utility functions share some common arguments, and sometimes I need to call more than one of these functions, which would repeat a few arguments and would throw an exception. How can I handle this case? (I'm looking for something more principled than just a generic try except, ideally some function like has_arg as in my example)

possible variables of a method on python modules

I am new to python, my question is that how we can know the arguments name of a method on a module. for example, in the smpplib module page (https://github.com/podshumok/python-smpplib) i see an example code that has a line as below
client.bind_transceiver(system_id='login', password='secret')
I what to know how i can know that bind_transceiver function has system_id password (and system_type) variable.
help (smpplib.client.Client) just give me below info about bind_transceiver
:
bind_transceiver(self, **args)
Bind as a transmitter and receiver at once
tl;dr You would have to look at the source code or documentation to find out.
Explanation: bind_transmitter uses **args. This allows the person calling the function to pass in any number of keyword arguments. For example:
bind_transmitter() # valid
bind_transmitter(a=1, b='4') # valid
bind_transmitter(myarg=1, m=6, y=7, system_id='me', password='secret') # valid
As such, there is no way to know which keyword arguments bind_transmitter will actually use without examining the source code or documentation.
Sometimes, in Python, functions have **kwargs argument that means "any key - value argument is accepted".
Key-value pairs are passed as in your example:
client.bind_transceiver(system_id='login', password='secret')
The function implementation code access the key - value pairs with the dictionary kwargs. So the internal code would do something like:
def bind_transceiver(self, **kwargs):
# Do something with system id
print(kwargs['system_id'])
# Do something with password
print(kwargs['password])
The output of your call to this function would be
login
password
You can pass any key-value pair to the function but there are only 2 ways to understand which keyes will be actually consumed.
Read the function documentation
Read the code
The keyes that are not used will be stored anyway in the dictionary args within the scope of the function but they won't have any effect.
It is also possible that the function accepts another argument like *args. This, instead of a dictionary, will be read like a tuple by the function in a similar fashion but using positional numbers as keyes.

Passing custom arguments to a Blender Operator as if it were a function

I created a python script in Blender which obtains information about an object. Said information is then stored in a list of numpy arrays for later use. Initially, I wanted to use that information to have the camera move in a certain way, but running the script freezes the 3D enviornment until the end of execution.
Multiple folks suggest using Operators, but Operators (as far as I know) only accept arguments in a very special and inconvenient way. For instance, here we have an example operator
import bpy
class DialogOperator(bpy.types.Operator):
bl_idname = "object.dialog_operator"
bl_label = "Simple Dialog Operator"
my_float = bpy.props.FloatProperty(name="Some Floating Point")
my_bool = bpy.props.BoolProperty(name="Toggle Option")
my_string = bpy.props.StringProperty(name="String Value")
def execute(self, context):
message = "Popup Values: %f, %d, '%s'" % \
(self.my_float, self.my_bool, self.my_string)
self.report({'INFO'}, message)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
bpy.utils.register_class(DialogOperator)
# test call
bpy.ops.object.dialog_operator('INVOKE_DEFAULT')
One can optionally write
# test call
bpy.ops.object.dialog_operator('INVOKE_DEFAULT',myfloat=2.3,...)
in order to set values for the parameters defined within the operator. My problem is that only fields within the operator of the form "bpy.props.****Property" can be assigned in this way.
Is there anyone who knows of a way to pass ANY desired set of argument to an operator in the same way arguments can be passed to a function?
NOTE: An ugly way I though of to pass the arguments indirectly is by declaring the set of variables you want to pass to be global variables...
To move your camera and get an updated 3DView, try using a modal operator. When execute() is called and it returns {'RUNNING_MODAL'}, the operators modal() method is called repeatedly until it returns {'FINISHED'} or {'CANCELLED'}. Within modal() you can change things and get the 3DView updated between calls.
Using bpy.props to add properties to an operator class is how you get properties that work within blender. While we can add normal properties that the operator can use within itself, we must use bpy.props to define the properties available to an operator that are set during the operator's execute and effect its operation, these properties can be adjusted by the user in the operator properties panel and effect the undo/redo of the operator.
There are "Vector" versions of Bool, Int and Float properties so you can have multiple values in one property. For more flexibility you might want to use a PropertyGroup or look at defining custom get and set functions.
Other options you have for storing values are adding properties to the scene or object, you can also use module properties (an addon is a python module) or use AddonPreferences.

python argparse: how to use other parsed argument as parameter at calling function in type keyword?

I am trying to create an user interface using argparse module.
One of the argument need to be converted, so I use the type keyword:
add_argument('positional', ..., type=myfunction)
and there is another optional argument:
add_argument('-s', dest='switch', ...)
in addition, I have
parsed_argument=parse_args()
However, in myfunction, I hope I can use an additional parameter to control the behavior, which is the optional argument above, i.e.
def myfunction(positional, switch=parsed_argument.switch):
...
How can I achieve that?
Simple answer: You can’t. The arguments are parsed separately, and there is no real guarantee that some order is maintained. Instead of putting your logic into the argument type, just store it as a string and do your stuff after parsing the command line:
parser.add_argument('positional')
parser.add_argument('-s', '--switch')
args = parser.parse_args()
myfunction(args.positional, switch=args.switch)
I'm not sure I did understand correctly what you want to achieve, but if what you want to do is something that looks like:
myprog.py cmd1 --switcha
myprog.py cmd2 --switchb
yes you can, you need to use subparsers. I wrote a good example of it for a little PoC I wrote to access stackoverflow's API from CLI. The whole logic is a bit long to put thoroughly here, but mainly the idea is:
create your parser using parser = argparse.ArgumentParser(...)
create the subparsers using subparsers = parser.add_subparsers(...)
add the commands with things like `subparser.add_parser('mycommand', help='Its only a command').set_defaults(func=mycmd_fn) where
mycmd_fn takes args as parameters where you have all the switches you issued to the command!
the difference from what you ask, is that you'll need one function per command, and not one function with the positional argument as first argument. But you can leverage that easily by having mycmd_fn being like: mycmd_fn = lambda *args: myfunction('mycmd', *args)
HTH
From the documentation:
type= can take any callable that takes a single string argument and returns the converted value:
Python functions like int and float are good examples of a type function should be like. int takes a string and returns a number. If it can't convert the string it raises a ValueError. Your function could do the same. argparse.ArgumentTypeError is another option. argparse isn't going to pass any optional arguments to it. Look at the code for argparse.FileType to see a more elaborate example of a custom type.
action is another place where you can customize behavior. The documentation has an example of a custom Action. Its arguments include the namespace, the object where the parser is collecting the values it will return to you. This object contains any arguments have already been set. In theory your switch value will be available there - if it occurs first.
There are many SO answers that give custom Actions.
Subparsers are another good way of customizing the handling of arguments.
Often it is better to check for the interaction of arguments after parse_args. In your case 'switch' could occur after the positional and still have effect. And argparse.Error lets you use the argparse error mechanism (e.g. displaying the usage)

Categories