Pythonic way to accept a tuple in a JSON argument - python

I have a script which accepts, on the command line, arguments to create a matplotlib.patches.Rectangle object.
Typical arguments that could be passed to the Rectangle constructor might be something like (in Python, without argument parsing):
patches.Rectangle(
xy=(15000,0),
width=9.4,
height=4,
color='peachpuff'
)
The user can encode this on the command line like so:
--patches '[{ "x" : 15000,"y" : 0,"width" : 9.4,"height" : 4,"color" : "peachpuff"}]'
This json array is loaded with json.loads. Note that most of the arguments can just be passed directly through to the Rectangle constructor, but the xy tuple causes a problem: json.loads will never generate a tuple type, so you can't create a tuple this way.
To work around it, I have the user pass separate x and y mappings, and then combine them like this:
p.add_argument('--patches', help='A JSON array of patches.Rectangle object arguments to patch onto the chart',
type=json.loads)
# stuff
# put some patches on
if args.patches:
def from_json(json):
ret = dict(json)
del ret['x']
del ret['y']
ret['xy'] = (json['x'], json['y'])
return ret
for pjson in args.patches:
p = from_json(pjson)
ax.add_patch(patches.Rectangle(**p))
Half of that code (essentially all of from_json) is just dealing with the transformation of x and y args into the xy = (x, y) tuple.
Any more elegant/Pythonic way to deal with this?
It might involve cleaner handling of the json input, or perhaps passing something other than json on the command line (but it must have rough feature-parity with the current approach).

Your from_json function can effectively be used as a custom type for your --patches option.
def RectangleArgs(s):
d = json.loads(s)
d['xy'] = (d.pop('x'), d.pop('y'))
return d
...
parser.add_argument('--patches', action='append', type=RectangleArgs)
...
for patch in args.patches:
ax.add_patch(patches.Rectangle(**patch))
Aside from simply massaging the JSON read from the command line, you can do additional validation, raising argparse.ArgumentTypeError if you find anything that would cause a problem when you try to use the return value in a call to patches.Rectangle.

Your motivating assumption that you need a tuple for the xy argument to Rectangle is not correct: a 2-element list works just as well, so "xy" : "[1, 2]" will work fine.
Pointed out by Jeff in the comments.

Related

Python OPC-UA call method with array input argument

I am very new to OPC UA, so please excuse if this is a very basic question.
I want to call a method with two input arguments. The input arguments are described as follows:
What I have tried is this:
evse = client.get_node("ns=6;s=::EVSE")
set_default = client.get_node("ns=6;s=::EVSE:SetDefault")
res = evse.call_method(set_default, 1, 20)
Which yields this error:
BadInvalidArgument: "One or more arguments are invalid."(BadInvalidArgument)
If I replace the last line by this
res = evse.call_method(set_default, [1, 20])
I get this error:
BadArgumentsMissing: "The client did not specify all of the input arguments for the method."(BadArgumentsMissing)
When I call the method manually from UaExpert, it works fine.
Can somebody give me a hint how to proceed?
Edit:
These are the argument structures expanded:
Does the Python library automatically cast the method arguments for you?
Maybe you're just not providing values of the correct datatype in your first attempt; try this instead:
res = evse.call_method(set_default, 1, 20.0)
Or look into the documentation or source code to see if this is required of you. I also don't know if there's a special unsigned "wrapper" class you would need to use for the first argument if the library doesn't do this casting for you.

How to write regexp for getting one specific function parameters

I'm trying to write an python script to collect one specific function's parameters.
Parameters can be in multiple lines like this:
str = "getParameters(['ABCD_1','ABCD_2',\
'ABCD_3','ABCD_4'])\
This works already: (it can catch every words between ' and '):
parameters = re.findall(r'\'[\w-]+\'', str)
for parameter in parameters:
print parameter
But I want that only in case of getParameters function the parameters to be collect, and this does not work:
getparameters = re.findall(r'getParameters\(\[[\w-]+', str, re.X|re.DOTALL)
for line in getparameters:
print line
Please suggest!
Here is an example using ast, just for fun.
import ast
module = ast.parse(
"""getParameters(['ABCD_1','ABCD_2',
'ABCD_3','ABCD_4'])""")
for item in module.body:
if isinstance(item.value, ast.Call) and item.value.func.id == 'getParameters':
parameters = [each.s for each in item.value.args[0].elts]
print parameters
If you're fixed on using RegEx and if your function occurs exactly once, you can try:
re.findall('\'(\w+)\',?', re.search('(getParameters\(.+?\))', x, re.X|re.S).group(1), re.X|re.S)
It's not ideal, but it works. I am sure there is a better way to do this.

Default parameter in python with objects

I know I can define default parameters in python, but can I do so with objects?
For example, I'd like to work with a p.expect object:
def exitDevice(ip, m='', sendExit=True):
if sendExit:
m.send('exit')
print "left device", ip
Is that the correct way to handle an object passed in as a default argument in Python? If not, how does one do so? Or if this is correct is there a better way to do so?
It's a bit tricky, since the default value has to be defined at the time your code is parsed, but you can always do something like this:
def exitDevice(ip,m=None,sendExit=True):
if m is None: m = getDefaultValueForM()
if sendExit: m.send ( 'exit' )

Using Deque.popleft as args for function

I am attempting to store a list of commands to send down a serial cable using deque in Python.
My function "send_command" accepts 3 values; The command, an int. pause and a boolean wait. its definition is as follows.
def send_command(self, command, pause=0, wait=False):
What I would like to do is, rather than calling this function like so:
send_command("A234", 5, True)
... or...
send_command("B4242")
I'd like to be able to store up commands inside a deque list and use the popleft function to then call my function. This would allow me to do things such as:
CommandList = deque((['A234', 5, True], ['B4242']))
...and use the deque.append() as a way to keep adding things to the list, which would then in turn be sent to my send_command function. I could then drop in and out of the list new commands when they are needed (or as quickly as the serial part of my code can do).
The part I'm struggling with is actually using the CommandList.popleft, or any part of the deque list as the args for my send_command function. It doesn't seem as straight forward as:
send_command(CommandList.popleft)
I'm sure it's something simple, but I cannot figure it out.
Can anyone help?
Thank-you kindly.
Andy
probably you need something like:
obj.send_command(*CommandList.popleft())
That is, call popleft and use the result as an argument list for send_command. self argument suggests this is a member function, so you need to call it on an object
Another way, as I wrote in the comment, is to store prepared functions with something like this:
def make_command(obj, *args, **kwargs):
def f():
obj.send_command(*args, **kwargs)
return f
Then you can do
queue.append(make_command(obj, 'ABC', whatever='else'))
and then execute:
command = queue.popleft()
command()
unbeli is right - you need the () to call the function, and you need * to unpack the arguments. However, there's no need for using deque when you can just do this:
commandlist = [['A234', 5, True], ['B4242'], ['A234', 0]]
for command in commandlist:
send_command(*command)
and that will work perfectly fine. For more info, see unpacking argument lists.
Queues are really only necessary if you're doing something in which you want to consume the values - say you want your commandlist to be empty when you're done. Of course you could also do the same thing with a list:
q = [1,2,3,4]
while q:
print q.pop(0)
print q
HTH
Have you tried:
send_command(CommandList.popleft()) # note the ()

Passing a list to eval()

Is there a way to pass a list as a function argument to eval() Or do I have to convert it to a string and then parse it as a list in the function?
My simple example looks like:
eval("func1(\'" + fArgs + "\')")
I'm just not sure if there is a better way of taking fArgs as a list instead of a string
Note:
The list is provided from a JSON response
EDIT: Ok here's a bit more of my class so there's a better understanding of how I'm using eval
def test(arg):
print arg
#Add all allowed functions to this list to be mapped to a dictionary
safe_list = ['test']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
class Validate:
def __init__(self, Value, fName, fArgs):
eval(fName + "(\'" + fArgs + "\')", {"__builtins__":None},safe_dict)
I may be wrong in thinking this, but to my understanding this is a safe use of eval because the only functions that can be called are the ones that are listed in the safe_list dictionary. The function to be run and the arguments for that function are being extracted out of a JSON object. The arguments are to be structured as a list, Will joining the list together with ", " be interpreted as actual arguments or just a single argument?
If you're using Python 2.6.x, then you should be able to use the json module (see py doc 19.2). If not, then there is python-json available through the python package index. Both of these packages will provide a reader for parsing JSON data into an appropriate Python data type.
For your second problem of calling a function determined by a message, you can do the following:
def foo():
print 'I am foo!'
def bar():
pass
def baz():
pass
funcs = {'func_a':foo, 'func_b':bar, 'func_c':baz}
funcs['func_a']()
This approach can be a bit more secure than eval because it prevents 'unsafe' python library functions from being injected into the JSON. However, you still need to be cautious that the data supplied to your functions can't be manipulated to cause problems.
Specifying parameters the following way works:
root#parrot$ more test.py
def func1(*args):
for i in args:
print i
l = [1,'a',9.1]
func1(*l)
root#parrot$ python test.py
1
a
9.1
so, no direct need for eval(), unless I'm misunderstanding something.
Using a library to parse JSON input may be a better approach than eval, something like:
import json
func1(json.loads(fArgs))
Assert-ing that user input is correct would be a good idea, too.
The others have a good point, that you shouldn't be using eval. But, if you must:
eval("func1(%s)" % ", ".join(fArgs))
will call the function with all the arguments in the list. This:
eval("func1([%s])" % ", ".join(fArgs))
will call it with the list of arguments in just one argument. Maybe you even want this?
eval("func1([%s])" % ", ".join(map(eval, fArgs)))
which would eval the arguments as well?

Categories

Resources