diff --git a/ordr/models/meta.py b/ordr/models/meta.py index 491d497..3c54a45 100644 --- a/ordr/models/meta.py +++ b/ordr/models/meta.py @@ -1,5 +1,10 @@ +''' SQL conventions and custom Encoders ''' + +import json + from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.schema import MetaData +from sqlalchemy.types import TypeDecorator, Unicode # Recommended naming convention used by Alembic, as various different database # providers will autogenerate vastly different names making migrations more @@ -14,3 +19,21 @@ NAMING_CONVENTION = { metadata = MetaData(naming_convention=NAMING_CONVENTION) Base = declarative_base(metadata=metadata) + + +class JsonEncoder(TypeDecorator): + ''' Custom type for storing data structures as json-encoded string. ''' + + impl = Unicode + + def process_bind_param(self, value, dialect): + ''' inbound (to database) ''' + if value is not None: + value = json.dumps(value) + return value + + def process_result_value(self, value, dialect): + ''' outbound (from database) ''' + if value is not None: + value = json.loads(value) + return value diff --git a/ordr/tests/models/__init__.py b/ordr/tests/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ordr/tests/models/meta.py b/ordr/tests/models/meta.py new file mode 100644 index 0000000..f6fdb58 --- /dev/null +++ b/ordr/tests/models/meta.py @@ -0,0 +1,35 @@ +import pytest + + +@pytest.mark.parametrize( + 'value,expected', [ + (None, None), + ([1, 2, 3], '[1, 2, 3]'), + ({'a':1, 'b':2}, '{"a": 1, "b": 2}'), + ] + ) +def test_json_encoder_bind(value, expected): + from ordr.models.meta import JsonEncoder + encoder = JsonEncoder() + assert encoder.process_bind_param(value, None) == expected + + +@pytest.mark.parametrize( + 'value,expected', [ + (None, None), + ('[1, 2, 3]', [1, 2, 3]), + ('{"a": 1, "b":2}', {'a':1, 'b':2}), + ] + ) +def test_json_encoder_result(value, expected): + from ordr.models.meta import JsonEncoder + encoder = JsonEncoder() + assert encoder.process_result_value(value, None) == expected + + +@pytest.mark.parametrize('value', [None, [1, 2, 3], {'a':1, 'b':2}]) +def test_json_encoder_bind_and_result(value): + from ordr.models.meta import JsonEncoder + encoder = JsonEncoder() + result = encoder.process_bind_param(value, None) + assert encoder.process_result_value(result, None) == value