diff options
author | Bob Ippolito <bob@redivi.com> | 2011-12-29 10:38:45 -0800 |
---|---|---|
committer | Bob Ippolito <bob@redivi.com> | 2011-12-29 10:38:45 -0800 |
commit | 4221f71f9bb6fc1488c70eec5a22fc96b2121e1a (patch) | |
tree | 043d7d6af517f4c90af5b17d1a4d9962a9f12f43 | |
parent | 7e0d1aaad867d37f12d9cd939e01a3ffff2dbacb (diff) | |
download | simplejson-dict-subclass-gh26.tar.gz |
callable check for _asdict with namedtuple_as_objectdict-subclass-gh26
-rw-r--r-- | CHANGES.txt | 8 | ||||
-rw-r--r-- | conf.py | 2 | ||||
-rw-r--r-- | simplejson/__init__.py | 2 | ||||
-rw-r--r-- | simplejson/_speedups.c | 10 | ||||
-rw-r--r-- | simplejson/encoder.py | 82 | ||||
-rw-r--r-- | simplejson/tests/test_namedtuple.py | 30 |
6 files changed, 92 insertions, 42 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index eea200a..44d67a3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,12 @@ +Version 2.3.1 released 2011-12-29 + +* namedtuple_as_object now checks _asdict to ensure that it + is callable. + https://github.com/simplejson/simplejson/issues/26 + Version 2.3.0 released 2011-12-05 -* Any objects with _asdict() methods are now considered for +* Any objects with _asdict() methods are now considered for namedtuple_as_object. https://github.com/simplejson/simplejson/pull/22 @@ -44,7 +44,7 @@ copyright = '2011, Bob Ippolito' # The short X.Y version. version = '2.3' # The full version, including alpha/beta/rc tags. -release = '2.3.0' +release = '2.3.1' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/simplejson/__init__.py b/simplejson/__init__.py index 50886f6..3f7b5a9 100644 --- a/simplejson/__init__.py +++ b/simplejson/__init__.py @@ -97,7 +97,7 @@ Using simplejson.tool from the shell to validate and pretty-print:: $ echo '{ 1.2:3.4}' | python -m simplejson.tool Expecting property name: line 1 column 2 (char 2) """ -__version__ = '2.3.0' +__version__ = '2.3.1' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', diff --git a/simplejson/_speedups.c b/simplejson/_speedups.c index 5169923..b6c7b1d 100644 --- a/simplejson/_speedups.c +++ b/simplejson/_speedups.c @@ -169,7 +169,15 @@ _is_namedtuple(PyObject *obj); static int _is_namedtuple(PyObject *obj) { - return PyObject_HasAttrString(obj, "_asdict"); + int rval = 0; + PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict"); + if (_asdict == NULL) { + PyErr_Clear(); + return 0; + } + rval = PyCallable_Check(obj); + Py_DECREF(obj); + return rval; } static int diff --git a/simplejson/encoder.py b/simplejson/encoder.py index 383d834..8a26141 100644 --- a/simplejson/encoder.py +++ b/simplejson/encoder.py @@ -157,7 +157,7 @@ class JSONEncoder(object): If namedtuple_as_object is true (the default), objects with ``_asdict()`` methods will be encoded as JSON objects. - + If tuple_as_array is true (the default), tuple (and subclasses) will be encoded as JSON arrays. """ @@ -387,15 +387,17 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield buf if isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) - elif (_namedtuple_as_object and hasattr(value, '_asdict')): - chunks = _iterencode_dict(value._asdict(), - _current_indent_level) - elif _tuple_as_array and isinstance(value, tuple): - chunks = _iterencode_list(value, _current_indent_level) - elif isinstance(value, dict): - chunks = _iterencode_dict(value, _current_indent_level) else: - chunks = _iterencode(value, _current_indent_level) + _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) + if _asdict and callable(_asdict): + chunks = _iterencode_dict(_asdict(), + _current_indent_level) + elif _tuple_as_array and isinstance(value, tuple): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) for chunk in chunks: yield chunk if newline_indent is not None: @@ -471,15 +473,17 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, else: if isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) - elif (_namedtuple_as_object and hasattr(value, '_asdict')): - chunks = _iterencode_dict(value._asdict(), - _current_indent_level) - elif _tuple_as_array and isinstance(value, tuple): - chunks = _iterencode_list(value, _current_indent_level) - elif isinstance(value, dict): - chunks = _iterencode_dict(value, _current_indent_level) else: - chunks = _iterencode(value, _current_indent_level) + _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) + if _asdict and callable(_asdict): + chunks = _iterencode_dict(_asdict(), + _current_indent_level) + elif _tuple_as_array and isinstance(value, tuple): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) for chunk in chunks: yield chunk if newline_indent is not None: @@ -505,27 +509,29 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif isinstance(o, list): for chunk in _iterencode_list(o, _current_indent_level): yield chunk - elif (_namedtuple_as_object and hasattr(o, '_asdict')): - for chunk in _iterencode_dict(o._asdict(), _current_indent_level): - yield chunk - elif (_tuple_as_array and isinstance(o, tuple)): - for chunk in _iterencode_list(o, _current_indent_level): - yield chunk - elif isinstance(o, dict): - for chunk in _iterencode_dict(o, _current_indent_level): - yield chunk - elif _use_decimal and isinstance(o, Decimal): - yield str(o) else: - if markers is not None: - markerid = id(o) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = o - o = _default(o) - for chunk in _iterencode(o, _current_indent_level): - yield chunk - if markers is not None: - del markers[markerid] + _asdict = _namedtuple_as_object and getattr(o, '_asdict', None) + if _asdict and callable(_asdict): + for chunk in _iterencode_dict(_asdict(), _current_indent_level): + yield chunk + elif (_tuple_as_array and isinstance(o, tuple)): + for chunk in _iterencode_list(o, _current_indent_level): + yield chunk + elif isinstance(o, dict): + for chunk in _iterencode_dict(o, _current_indent_level): + yield chunk + elif _use_decimal and isinstance(o, Decimal): + yield str(o) + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = _default(o) + for chunk in _iterencode(o, _current_indent_level): + yield chunk + if markers is not None: + del markers[markerid] return _iterencode diff --git a/simplejson/tests/test_namedtuple.py b/simplejson/tests/test_namedtuple.py index f04d96f..54a9a12 100644 --- a/simplejson/tests/test_namedtuple.py +++ b/simplejson/tests/test_namedtuple.py @@ -35,6 +35,18 @@ class DuckPoint(object): def _asdict(self): return self.point._asdict() +class DeadDuck(object): + _asdict = None + +class DeadDict(dict): + _asdict = None + +CONSTRUCTORS = [ + lambda v: v, + lambda v: [v], + lambda v: [{'key': v}], +] + class TestNamedTuple(unittest.TestCase): def test_namedtuple_dumps(self): for v in [Value(1), Point(1, 2), DuckValue(1), DuckPoint(1, 2)]: @@ -89,3 +101,21 @@ class TestNamedTuple(unittest.TestCase): json.loads(sio.getvalue())) self.assertRaises(TypeError, json.dump, v, StringIO(), tuple_as_array=False, namedtuple_as_object=False) + + def test_asdict_not_callable_dump(self): + for f in CONSTRUCTORS: + self.assertRaises(TypeError, + json.dump, f(DeadDuck()), StringIO(), namedtuple_as_object=True) + sio = StringIO() + json.dump(f(DeadDict()), sio, namedtuple_as_object=True) + self.assertEqual( + json.dumps(f({})), + sio.getvalue()) + + def test_asdict_not_callable_dumps(self): + for f in CONSTRUCTORS: + self.assertRaises(TypeError, + json.dumps, f(DeadDuck()), namedtuple_as_object=True) + self.assertEqual( + json.dumps(f({})), + json.dumps(f(DeadDict()), namedtuple_as_object=True)) |