From ef43c2dff7ec41a58945600027e2150629fd8c48 Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Mon, 14 Nov 2022 09:59:31 -0800 Subject: Implement tests and fallback implementation of #301 --- simplejson/encoder.py | 45 ++++++++++++++++++++++--------------- simplejson/tests/test_namedtuple.py | 29 ++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 20 deletions(-) (limited to 'simplejson') diff --git a/simplejson/encoder.py b/simplejson/encoder.py index 2f81cab..2bc8bba 100644 --- a/simplejson/encoder.py +++ b/simplejson/encoder.py @@ -450,6 +450,15 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, not isinstance(_int_as_string_bitcount, integer_types))): raise TypeError("int_as_string_bitcount must be a positive integer") + def call_method(obj, method_name): + method = getattr(obj, method_name, None) + if callable(method): + try: + return (method(),) + except TypeError: + pass + return None + def _encode_int(value): skip_quoting = ( _int_as_string_bitcount is None @@ -512,15 +521,15 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield buf + str(value) else: yield buf - for_json = _for_json and getattr(value, 'for_json', None) - if for_json and callable(for_json): - chunks = _iterencode(for_json(), _current_indent_level) + for_json = _for_json and call_method(value, 'for_json') + if for_json: + chunks = _iterencode(for_json[0], _current_indent_level) elif isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) else: - _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) - if _asdict and callable(_asdict): - dct = _asdict() + _asdict = _namedtuple_as_object and call_method(value, '_asdict') + if _asdict: + dct = _asdict[0] if not isinstance(dct, dict): raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) chunks = _iterencode_dict(dct, @@ -636,15 +645,15 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif _use_decimal and isinstance(value, Decimal): yield str(value) else: - for_json = _for_json and getattr(value, 'for_json', None) - if for_json and callable(for_json): - chunks = _iterencode(for_json(), _current_indent_level) + for_json = _for_json and call_method(value, 'for_json') + if for_json: + chunks = _iterencode(for_json[0], _current_indent_level) elif isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) else: - _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) - if _asdict and callable(_asdict): - dct = _asdict() + _asdict = _namedtuple_as_object and call_method(value, '_asdict') + if _asdict: + dct = _asdict[0] if not isinstance(dct, dict): raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) chunks = _iterencode_dict(dct, @@ -682,17 +691,17 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif isinstance(o, float): yield _floatstr(o) else: - for_json = _for_json and getattr(o, 'for_json', None) - if for_json and callable(for_json): - for chunk in _iterencode(for_json(), _current_indent_level): + for_json = _for_json and call_method(o, 'for_json') + if for_json: + for chunk in _iterencode(for_json[0], _current_indent_level): yield chunk elif isinstance(o, list): for chunk in _iterencode_list(o, _current_indent_level): yield chunk else: - _asdict = _namedtuple_as_object and getattr(o, '_asdict', None) - if _asdict and callable(_asdict): - dct = _asdict() + _asdict = _namedtuple_as_object and call_method(o, '_asdict') + if _asdict: + dct = _asdict[0] if not isinstance(dct, dict): raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) for chunk in _iterencode_dict(dct, _current_indent_level): diff --git a/simplejson/tests/test_namedtuple.py b/simplejson/tests/test_namedtuple.py index 8035a5f..cc0f8aa 100644 --- a/simplejson/tests/test_namedtuple.py +++ b/simplejson/tests/test_namedtuple.py @@ -110,22 +110,47 @@ class TestNamedTuple(unittest.TestCase): def test_asdict_not_callable_dump(self): for f in CONSTRUCTORS: - self.assertRaises(TypeError, - json.dump, f(DeadDuck()), StringIO(), namedtuple_as_object=True) + 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()) + self.assertRaises( + TypeError, + json.dump, + f(Value), + StringIO(), + namedtuple_as_object=True + ) def test_asdict_not_callable_dumps(self): for f in CONSTRUCTORS: self.assertRaises(TypeError, json.dumps, f(DeadDuck()), namedtuple_as_object=True) + self.assertRaises( + TypeError, + json.dumps, + f(Value), + namedtuple_as_object=True + ) self.assertEqual( json.dumps(f({})), json.dumps(f(DeadDict()), namedtuple_as_object=True)) + def test_asdict_unbound_method_dumps(self): + for f in CONSTRUCTORS: + self.assertEqual( + json.dumps(f(Value), default=lambda v: v.__name__), + json.dumps(f(Value.__name__)) + ) + def test_asdict_does_not_return_dict(self): if not mock: if hasattr(unittest, "SkipTest"): -- cgit v1.2.1