SQLALCHEMY的JSON支持

解决5.6以下sqlalchemy不支持JSON的问题

JSON是一种使用相当普遍的数据协议类型,对于保存数据十分方便,但是老版本的MySQL又不支持JSON格式,所以只能使用TEXT来保存JSON类型的字符串。

原始方案

对于字符串类型的JSON,在使用的时候必须进行一步转化,把字符串转化为Python中的DICT,为了能够方便的使用SQLALCHEMY,遂写了如下的方法:

import json

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Insert


# !! 实例化后的用法等同于内置的 dict
class JsonValue(object):

    def __init__(self, attr_name):
        self.attr_name = attr_name
        self.name = '_' + attr_name

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.name, None)
        if value is None:
            _v = getattr(obj, self.attr_name)
            if not _v:
                value = {}
                obj.__dict__[self.name] = value
                return value
            value = json.loads(_v)
            obj.__dict__[self.name] = value
        return value

    def __set__(self, instance, value):
        if value is None:
            return
        if not isinstance(value, dict):
            raise ValueError('value should be a dict')
        setattr(instance, self.attr_name, json.dumps(value, indent=2, sort_keys=True))
        instance.__dict__[self.name] = value


class JSONDataMixin(object):

    data_dict = JsonValue('data')

    def update_json(self, name, json_value):
        obj = getattr(self, name, None)
        if obj is None:
            raise KeyError(name)
        obj.update(json_value)
        setattr(self, name, obj)
        

使用时,只需要新增一列属性或者类中继承JSONDataMixin即可,使用时,直接操作data_dict或者其他自定义的名字。如下:

class MyTable(Base, JSONDataMixin):
    id = Column(BigInteger, primary_key=True)
    data = Column(Text)
或
class MyTable(Base):
    id = Column(BigInteger, primary_key=True)
    data = Column(Text)
    data_dict = JsonValue('data')

但是此方法有个弊端,由于修改了data_dict之后,需要显示的调用obj.data_dict = obj.data_dict来触发(否则data值无改动),故用起来颇为麻烦。

新的方案

为了解决上述弊端,所以有了如下的方案:


from sqlalchemy.types import TypeDecorator, VARCHAR
from sqlalchemy.ext.mutable import Mutable
import json


class JSONEncodedDict(TypeDecorator):
    """Representes an immutable structure as a json-encoded string"""

    impl = VARCHAR

    def process_bind_param(self, value, dialect):
        if value:
            value = json.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        if value:
            value = json.loads(value)
        return value or dict()


class MutableDict(Mutable, dict):

    @classmethod
    def coerce(cls, key, value):
        """Convert plain dictionaries to MutableDict"""

        if not isinstance(value, MutableDict):
            if isinstance(value, dict):
                return MutableDict(value)

            return Mutable.coerce(key, value)
        else:
            return value

    def __setitem__(self, key, value):
        """Detect dictionary set events and emit change events"""

        dict.__setitem__(self, key, value)
        self.changed()

    def __delitem__(self, key):
        """Detect dictionary del events and emit change events"""

        dict.__delitem__(self, key)
        self.changed()


MutableDict.associate_with(JSONEncodedDict)

有了上面的方法,在代码中只需要用 JSONEncodedDict 当 Column来使用就行了,例如:

class MyTable(Base):
    id = Column(BigInteger, primary_key=True)
    data = Column(JSONEncodedDict)

这样就可以直接用data了,完美~

你可能感兴趣的:(python,后台开发)