diff --git a/pecan/jsonify.py b/pecan/jsonify.py index 300666b..60e4883 100644 --- a/pecan/jsonify.py +++ b/pecan/jsonify.py @@ -5,10 +5,16 @@ except ImportError: from datetime import datetime, date from decimal import Decimal -from webob.multidict import MultiDict -from sqlalchemy.engine.base import ResultProxy, RowProxy +from webob.multidict import MultiDict, UnicodeMultiDict from simplegeneric import generic +try: + from sqlalchemy.engine.base import ResultProxy, RowProxy +except ImportError: #pragma no cover + # dummy classes since we don't have SQLAlchemy installed + class ResultProxy: pass + class RowProxy: pass + # # exceptions # @@ -32,6 +38,9 @@ class GenericJSON(JSONEncoder): elif isinstance(obj, (date, datetime)): return str(obj) elif isinstance(obj, Decimal): + # XXX What to do about JSONEncoder crappy handling of Decimals? + # SimpleJSON has better Decimal encoding than the std lib + # but only in recent versions return float(obj) elif is_saobject(obj): props = {} @@ -43,7 +52,7 @@ class GenericJSON(JSONEncoder): return dict(rows=list(obj), count=obj.rowcount) elif isinstance(obj, RowProxy): return dict(rows=dict(obj), count=1) - elif isinstance(obj, MultiDict): + elif isinstance(obj, (MultiDict, UnicodeMultiDict)): return obj.mixed() else: return JSONEncoder.default(self, obj) @@ -63,6 +72,4 @@ _instance = GenericFunctionJSON() def encode(obj): - if isinstance(obj, basestring): - return _instance.encode(obj) return _instance.encode(obj) diff --git a/tests/test_jsonify.py b/tests/test_jsonify.py index 6d560e5..7173cf9 100644 --- a/tests/test_jsonify.py +++ b/tests/test_jsonify.py @@ -1,8 +1,12 @@ -from pecan.jsonify import jsonify, encode -from pecan import Pecan, expose -from webtest import TestApp -from json import loads +from datetime import datetime, date +from decimal import Decimal +from json import loads +from unittest import TestCase +from pecan.jsonify import jsonify, encode +from pecan import Pecan, expose, request +from webtest import TestApp +from webob.multidict import MultiDict, UnicodeMultiDict def make_person(): class Person(object): @@ -59,3 +63,43 @@ class TestJsonify(object): r = app.get('/') assert r.status_int == 200 assert loads(r.body) == {'name':'Jonathan LaCour'} + +class TestJsonifyGenericEncoder(TestCase): + def test_json_callable(self): + class JsonCallable(object): + def __init__(self, arg): + self.arg = arg + def __json__(self): + return {"arg":self.arg} + + result = encode(JsonCallable('foo')) + assert loads(result) == {'arg':'foo'} + + def test_datetime(self): + today = date.today() + now = datetime.now() + + result = encode(today) + assert loads(result) == str(today) + + result = encode(now) + assert loads(result) == str(now) + + def test_decimal(self): + # XXX Testing for float match which is inexact + + d = Decimal('1.1') + result = encode(d) + assert loads(result) == float(d) + + def test_multidict(self): + md = MultiDict() + md.add('arg', 'foo') + md.add('arg', 'bar') + result = encode(md) + assert loads(result) == {'arg': ['foo', 'bar']} + + def test_fallback_to_builtin_encoder(self): + class Foo(object): pass + + self.assertRaises(TypeError, encode, Foo())