Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

isinstance not working for Decimal in AppEngine

I have an instance of decimal.Decimal which comes from an SQLAlchemy query. As I need to serialize the object, I have created a JSON serializer to deal with the Decimal:

import decimal
class AlchemyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, decimal.Decimal):
            return str(obj)
        return json.JSONEncoder.default(self, obj)

The unfortunate thing is that the isinstance(obj, decimal.Decimal) does not return a True for the instance, even though (using pdb in the default method above):

obj.__class__ # => <class 'decimal.Decimal'>
blah = decimal.Decimal()
blah.__class__ # => <class 'decimal.Decimal'>
isinstance(obj, decimal.Decimal) # => False
isinstance(blah, decimal.Decimal) # => True
isinstance(obj, obj.__class__) # => True

I did check that the module both instances refer to is the same module:

import inspect
inspect.getfile(obj.__class__) # => '/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.pyc'
inspect.getfile(blah.__class__) # => '/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.pyc'

I would really love to understand why this is not working!

EDIT

It turns out that the issue only occurs when running under AppEngine's dev_appserver.py environment. A simple:

isinstance(db.session.execute('SELECT amount FROM model LIMIT 1').fetchone()[0], decimal.Decimal)

returns False when making a request via the AppEngine dev_appserver and True when run from the console.

like image 208
Aert Avatar asked Dec 22 '16 00:12

Aert


1 Answers

Ran into this today, and came across an old mailing list post that discussed this.

Turns out this question has also been addressed on stackoverflow previously as well. Pasting the answer here for ease of access / reduced stackoverflow server load:


It appears that the decimal.Decimal class is patched somewhere in the Google App Engine SDK (or the module is reloaded), and this is done between the MySQL conversion library importing decimal and you importing the same.

Luckily, we can work around this by updating MySQL conversion table:

from MySQLdb.constants import FIELD_TYPE
from MySQLdb.converters import conversions
import decimal

conversions[FIELD_TYPE.DECIMAL] = conversions[FIELD_TYPE.NEWDECIMAL] = decimal.Decimal

That's it; the above code resets the MySQL class and your JSON encoder type check succeeds.

The alternative would be for you to look for the class MySQLdb is using:

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, MySQLdb.converters.conversions[MySQLdb.constants.FIELD_TYPE.DECIMAL]):
            return float(o)
        return super(DecimalEncoder, self).default(o)

like image 184
matt Avatar answered Nov 20 '22 18:11

matt