summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Ippolito <bob@redivi.com>2022-11-14 09:59:31 -0800
committerBob Ippolito <bob@redivi.com>2022-11-14 09:59:31 -0800
commitef43c2dff7ec41a58945600027e2150629fd8c48 (patch)
tree8f86f9741772d036b80f4b0160e7008883970506
parent43645252224d4e529c22cb78ac42f241d6426073 (diff)
downloadsimplejson-ef43c2dff7ec41a58945600027e2150629fd8c48.tar.gz
Implement tests and fallback implementation of #301
-rw-r--r--.github/workflows/build-and-deploy.yml2
-rw-r--r--CHANGES.txt5
-rw-r--r--simplejson/encoder.py45
-rw-r--r--simplejson/tests/test_namedtuple.py29
4 files changed, 59 insertions, 22 deletions
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index b4eb545..15ca1cc 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -65,7 +65,7 @@ jobs:
- uses: actions/setup-python@v2
name: Install Python
with:
- python-version: '3.10'
+ python-version: '3.11'
- name: Build sdist
run: python setup.py sdist
diff --git a/CHANGES.txt b/CHANGES.txt
index 22d3ded..7d574e2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,8 @@
-Version 3.17.7 released XXXX-XX-XX
+Version 3.18.0 released 2022-11-14
+* Allow serialization of classes that implement for_json or _asdict by
+ ignoring TypeError when those methods are called
+ https://github.com/simplejson/simplejson/pull/302
* Raise JSONDecodeError instead of ValueError in invalid unicode escape
sequence edge case
https://github.com/simplejson/simplejson/pull/298
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"):