Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to convert a string to the proper type for an NDB property?

If I have some (string) values from a GET or POST request with the associated Property instances, one IntegerProperty and one TextProperty, say, is there a way to convert the values to the proper (user) types without a long tedious chain of isinstance calls?

I'm looking to reproduce this sort of functionality (all input validation omitted for clarity):

for key, value in self.request.POST.iteritems():
    prop = MyModel._properties[key]

    if isinstance(prop, ndb.IntegerProperty):
        value = int(value)
    elif isinstance(prop, (ndb.TextProperty, ndb.StringProperty)):
        pass # it's already the right type
    elif ...
    else
        raise RuntimeError("I don't know how to deal with this property: {}"
                           .format(prop))

    setattr(mymodelinstance, key, value)

For example, if there is a way to get the int class from an IntegerProperty and the bool class from a BooleanProperty etc., that would do the job.

The ndb metadata API doesn't really solve this elegantly, as far as I can see; with get_representations_of_kind I can reduce the number of cases, though.

like image 888
Erik P. Avatar asked Mar 04 '15 17:03

Erik P.


2 Answers

You can use a dict to map between user-defined types to built-in types by using type of the object as a key and a built-in type as value.

F.e.

class IntegerProperty(int):
    pass

class StringProperty(str):
    pass

a, b  = IntegerProperty('1'), StringProperty('string')

def to_primitive(obj):
    switch = {IntegerProperty: int, StringProperty: str}
    return switch[type(obj)](obj)

for x in (a, b):
        print(to_primitive(x))

Because here the key is type of the object instead of isinstance check, if more than one user-defined types map to a single built-in type KeyError will arise if the type is not in the dict. So you have to explicitly add every user-defined type to the switch dict.

F.e.

class TextProperty(StringProperty):
    pass
switch = {IntegerProperty: int, StringProperty: str, TextProperty: str}

Above we have added the new TextProperty to the switch even though TextProperty is subclass of StringProperty. If you don't want to do that we have to get the key from isinstance check.
Here's how to do it;

class IntegerProperty(int):
    pass

class StringProperty(str):
    pass

class TextProperty(StringProperty):
    pass

a, b, c = IntegerProperty('1'), StringProperty('string'), TextProperty('text')

def to_primitive(obj):
    switch = {IntegerProperty: int, StringProperty: str}
    key = filter(lambda cls: isinstance(obj, cls), switch.keys())
    if not key:
        raise TypeError('Unknown type: {}'.format(repr(obj)))
    key = key[0]

    return switch[key](obj)

for x in (a, b, c):
        print(to_primitive(x))
like image 122
Nizam Mohamed Avatar answered Nov 10 '22 03:11

Nizam Mohamed


There is a Python package called WTForms that is enormously helpful for this, and generally makes form processing a much more pleasant experience.

Here is a really simple example:

class MyForm(wt.Form):
    text = MyTextAreaField("Enter text")
    n = wt.IntegerField("A number")

f = MyForm(self.request.POST)
f.validate()
print f.text.data
print f.n.data

Calling f.validate() will automatically convert the POST data to the data type specified by the form. So f.text.data will be a string and f.n.data will be an int.

It also gracefully handles invalid data. If a user inputs a letter for the integer field, then f.n.data will be None. You can also specify error messages that are easily incorporated into your web page.

WTForms takes a huge amount of drudgery out of form processing!

like image 31
gaefan Avatar answered Nov 10 '22 03:11

gaefan