summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Ippolito <bob@redivi.com>2011-12-29 10:38:45 -0800
committerBob Ippolito <bob@redivi.com>2011-12-29 10:38:45 -0800
commit4221f71f9bb6fc1488c70eec5a22fc96b2121e1a (patch)
tree043d7d6af517f4c90af5b17d1a4d9962a9f12f43
parent7e0d1aaad867d37f12d9cd939e01a3ffff2dbacb (diff)
downloadsimplejson-dict-subclass-gh26.tar.gz
callable check for _asdict with namedtuple_as_objectdict-subclass-gh26
-rw-r--r--CHANGES.txt8
-rw-r--r--conf.py2
-rw-r--r--simplejson/__init__.py2
-rw-r--r--simplejson/_speedups.c10
-rw-r--r--simplejson/encoder.py82
-rw-r--r--simplejson/tests/test_namedtuple.py30
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
diff --git a/conf.py b/conf.py
index 4b4537a..ac0185b 100644
--- a/conf.py
+++ b/conf.py
@@ -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))