-2

I'm not guru in python. I don't get why I can't assign an array of values to a key for this dictionnary with this def.

This is how I use the sdk definition.

order_obj = Order(None)
order_obj.customerIp("14.140.42.67")
order_obj.merchantRefNum(RandomTokenGenerator().generateToken())
order_obj.currencyCode("CAD")
order_obj.totalAmount("1125")

redirect = Redirect(None)
redirect.rel("on_success")
redirect.uri("http://outwander.ca:{}/redirect")
return_keys_list = []
return_keys_list.append('id')
return_keys_list.append('transaction.amount')
redirect.returnKeys(return_keys_list)
redirect_list = []
redirect_list.append(redirect.__dict__)
order_obj.redirect(redirect_list)

This is the specific definition in the sdk I use https://github.com/OptimalPayments/Python_SDK/blob/63e1ae662a4447bd65c907563eec9effc602dd74/src/PythonNetBanxSDK/HostedPayment/Redirect.py

'''
Property Return Keys
'''   
def returnKeys(self, return_keys):
    self.__dict__['returnKeys'] = return_keys

From what I could debug, the problems comes when converting the array into json to be sent.The loop finds the array and try to convert the elements inside (strings), but since by default the loop consider each given parameter as array, the returnKeys parameter is skipped...

'''
Serializing object
@param: Dictionary Object
@return: Serialized data
'''
def serialize(self, obj):
    return (json.dumps(self.to_dictionary(obj)))

'''
Convert object to a dictionary
@param: POJO Object
@return: Dictionary Object
'''
def to_dictionary(self, obj):
    obj_dict = dict()
    for key in obj.__dict__.keys():
        try:
            if(type(obj.__dict__[key]) is list):
                content = []
                for count in range(0, obj.__dict__[key].__len__()):
                    content.append(
                            self.to_dictionary(obj.__dict__[key][count]))
                obj_dict[key] = content
            elif(isinstance(obj.__dict__[key], DomainObject)):
                obj_dict[key] = self.to_dictionary(obj.__dict__[key])
            else:
                obj_dict[key] = obj.__dict__[key]
        except KeyError:
            pass
        except AttributeError:
            pass
    return (obj_dict)

I have no problem assigning a key to one value, but when I try assigning an array to a key, it doesn't work and it just ignore this key from the json output.

SOLUTION (HACK)

order_obj = Order(None)
order_obj.customerIp("14.140.42.67")
order_obj.merchantRefNum(RandomTokenGenerator().generateToken())
order_obj.currencyCode("CAD")
order_obj.totalAmount("1125")

redirect = Redirect(None)
redirect.rel("on_success")
redirect.uri("http://outwander.ca:{}/redirect")
redirect.returnKeys(('id', 'transaction.amount'))
redirect_list = []
redirect_list.append(redirect.__dict__)
order_obj.redirect((redirect_list))
fneron
  • 1,057
  • 3
  • 15
  • 39
  • 2
    So the problem is in some other code that you haven't included in the question? – khelwood Jun 08 '16 at 16:02
  • 1
    *the code throw an exception* - which code, and what exception? You should use `setattr()` in preference to `__dict__`. See http://stackoverflow.com/questions/432786/how-can-i-assign-a-new-class-attribute-via-dict-in-python – cdarke Jun 08 '16 at 16:03
  • @khelwood Included all I know. I'm not familiar with the SDK, but it's open source. – fneron Jun 08 '16 at 16:47
  • Please include the exception you are getting – rrauenza Jun 08 '16 at 17:11
  • I'm not getting any exception. It's just that I don't get the expected json string. Instead it's empty. I tried on a python compiler online to assign values to a key with a dict and it works... So my guess it's in the serialization. You might want to look in the to_dictionary definition... @rrauenza – fneron Jun 08 '16 at 17:13
  • Ok, I've reproduced this -- running through the debugger. – rrauenza Jun 08 '16 at 17:20
  • @rrauenza I believe the error reside in the serialization from the SDK. `for count in range(0, obj.__dict__[key].__len__()): content.append( self.to_dictionary(obj.__dict__[key][count])) obj_dict[key] = content` – fneron Jun 08 '16 at 17:28

1 Answers1

1

I've reproduced this:

import json

class S:

    def serialize(self, obj):
        return (json.dumps(self.to_dictionary(obj)))

    def to_dictionary(self, obj):
        obj_dict = dict()
        for key in obj.__dict__.keys():
            try:
                if(type(obj.__dict__[key]) is list):
                    content = []
                    for count in range(0, obj.__dict__[key].__len__()):
                        content.append(self.to_dictionary(obj.__dict__[key][count]))
                    obj_dict[key] = content
                # elif(isinstance(obj.__dict__[key], DomainObject)):
                #    obj_dict[key] = self.to_dictionary(obj.__dict__[key])
                else:
                    obj_dict[key] = obj.__dict__[key]
            except KeyError:
                pass
            except AttributeError:
                pass
        return (obj_dict)


class A:
    def __init__(self):
        self.a = "1234"                                                         
        self.b = (1, 2, 3, 4)                                                   
        self.c = [1, 2, 3, 4]                                                   
        self.d = { 1: 2, 3: 4}               

print S().serialize(A())

Output:

{"a": "1234", "b": [1, 2, 3, 4], "d": {"1": 2, "3": 4}}

The problem is that when the code recurses into to_dictionary() with the items in the list, the code always expects that it is recursing with a object that contains a __dict__. But neither a string (nor an int) contain a __dict__. (Which I see you've stated similarly in the question.)

I would file an issue on the github referring to this question.

Other than patching their code, your only option is to create a class that has the items you want. It appears they do not support a string or int except as a value in a __dict__.

You could use a dict as a list:

class ReturnList:
    def __init__(self):
        self.list = { 1: "Item1", 2: "Item2" }

redirect.returnKeys(ReturnList())

Addendum:

Wait! Why did the tuple case work?? I think you could do:

redirect.returnKeys((1,2,3,4))

Oh, I see why it works. It's a bug. They specifically handle list but not tuple, and it doesn't recurse for tuple types. Since the tuple contains directly json-able types, it just converts it to a json list. If your tuple contained a class or other more complex (non-json) objects, it would fail.

If you want to patch their code locally, or ask them to patch their code, it could be modified like this to both accept tuples and to properly handle int and string:

if(isinstance(obj.__dict__[key], (list, tuple)):
    for count in range(0, obj.__dict__[key].__len__()):
        item = obj.__dict__[key][count])
        if not isinstance(item, (str, int)):
            item = self.to_dictionary(obj.__dict__[key][count])
        content.append(item)

For maintainability (in case you ever get a new version of their SDK), I'd probably figure out a way to use a dict instead of a list and just make the indexes into your dict be integers, similar to a list. i.e., foo = { 0: 'string1', 1: 'string2', 2: 'string3', }; redirect.returnKeys(foo)

A quick way to convert your list into a dict is with

l = ['string1', 'string2', ]
dictified_list = dict(zip(l, range(0, len(l))))
redirect.returnkeys(dictified_list)
rrauenza
  • 6,285
  • 4
  • 32
  • 57
  • I don't want my list to be a key. I want multiple values for one key.self.__dict__['returnKeys'] = return_keys where return_keys = an array of values – fneron Jun 08 '16 at 16:56
  • edited... but I'm going to need to look at the SDK you're using to better understand your problem. Can you include your error messages in the question? – rrauenza Jun 08 '16 at 17:07
  • How can I fix this recursive definition to handle strings? – fneron Jun 08 '16 at 17:46
  • Put them in a tuple. Until their SDK breaks that. Or put them in a dict that you use as a list. My examples used numbers, but strings work fine, too. – rrauenza Jun 08 '16 at 17:48
  • By fix do you mean fix the underlying SDK code? Or workaround by changing what you pass? – rrauenza Jun 08 '16 at 17:48
  • I actually imported the local SDK and want to change the code to make strings array works. – fneron Jun 08 '16 at 17:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114152/discussion-between-fneron-and-rrauenza). – fneron Jun 08 '16 at 18:07