diff --git a/setup.py b/setup.py index 54c4171..afa2336 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ extras_require = { 'flexmock>=0.9.7', 'psycopg2>=2.4.6', ], + 'anyjson': ['anyjson>=0.3.3'], 'babel': ['Babel>=1.3'], 'arrow': ['arrow>=0.3.4'], 'phone': [ @@ -57,7 +58,7 @@ for name, requirements in extras_require.items(): setup( name='SQLAlchemy-Utils', - version='0.19.0', + version='0.20.0', url='https://github.com/kvesteri/sqlalchemy-utils', license='BSD', author='Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen', diff --git a/sqlalchemy_utils/types/json.py b/sqlalchemy_utils/types/json.py new file mode 100644 index 0000000..bcd2d65 --- /dev/null +++ b/sqlalchemy_utils/types/json.py @@ -0,0 +1,51 @@ +import sqlalchemy as sa + +json = None +try: + import anyjson as json +except ImportError: + pass + +import six +from sqlalchemy.dialects.postgresql.base import ischema_names +from ..exceptions import ImproperlyConfigured + + +class PostgresJSONType(sa.types.UserDefinedType): + """ + Text search vector type for postgresql. + """ + def get_col_spec(self): + return 'json' + + +ischema_names['json'] = PostgresJSONType + + +class JSONType(sa.types.TypeDecorator): + "Represents an immutable structure as a json-encoded string." + + impl = sa.UnicodeText + + def __init__(self): + if json is None: + raise ImproperlyConfigured( + 'JSONType needs anyjson package installed.' + ) + + def load_dialect_impl(self, dialect): + if dialect.name == 'postgresql': + # Use the native JSON type. + return dialect.type_descriptor(PostgresJSONType()) + else: + return dialect.type_descriptor(self.impl) + + def process_bind_param(self, value, dialect): + if value is not None: + value = six.text_type(json.dumps(value)) + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value diff --git a/tests/types/test_json.py b/tests/types/test_json.py new file mode 100644 index 0000000..6686b26 --- /dev/null +++ b/tests/types/test_json.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from pytest import mark +import sqlalchemy as sa +from sqlalchemy_utils.types import json +from tests import TestCase + + +@mark.skipif('json.json is None') +class TestJSONType(TestCase): + def create_models(self): + class Document(self.Base): + __tablename__ = 'document' + id = sa.Column(sa.Integer, primary_key=True) + json = sa.Column(json.JSONType) + + self.Document = Document + + def test_parameter_processing(self): + document = self.Document( + json={'something': 12} + ) + + self.session.add(document) + self.session.commit() + + document = self.session.query(self.Document).first() + assert document.json == {'something': 12} + + def test_non_ascii_chars(self): + document = self.Document( + json={'something': u'äääööö'} + ) + + self.session.add(document) + self.session.commit() + + document = self.session.query(self.Document).first() + assert document.json == {'something': u'äääööö'}