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了,完美~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章