From cdeff390d9420e8ac09033c8195ac4b174790f4f Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 14 Oct 2021 23:52:53 -0700 Subject: [PATCH] Update simplejson-3.17.5 --- lib/simplejson/__init__.py | 4 +- lib/simplejson/_speedups.c | 11 +++++ lib/simplejson/encoder.py | 16 +++++-- lib/simplejson/tests/__init__.py | 55 ++++++++++++++++--------- lib/simplejson/tests/_cibw_runner.py | 7 ++++ lib/simplejson/tests/test_namedtuple.py | 27 ++++++++++++ 6 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 lib/simplejson/tests/_cibw_runner.py diff --git a/lib/simplejson/__init__.py b/lib/simplejson/__init__.py index 7b5687c8..c41029ec 100644 --- a/lib/simplejson/__init__.py +++ b/lib/simplejson/__init__.py @@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON):: """ from __future__ import absolute_import -__version__ = '3.17.0' +__version__ = '3.17.5' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', @@ -360,7 +360,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, If specified, *item_sort_key* is a callable used to sort the items in each dictionary. This is useful if you want to sort items other than - in alphabetical order by key. This option takes precendence over + in alphabetical order by key. This option takes precedence over *sort_keys*. If *sort_keys* is true (default: ``False``), the output of dictionaries diff --git a/lib/simplejson/_speedups.c b/lib/simplejson/_speedups.c index e7101288..6fefa997 100644 --- a/lib/simplejson/_speedups.c +++ b/lib/simplejson/_speedups.c @@ -386,6 +386,8 @@ static int _is_namedtuple(PyObject *obj) { int rval = 0; + /* We intentionally accept anything with a duck typed _asdict method rather + * than requiring it to pass PyTuple_Check(obj). */ PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict"); if (_asdict == NULL) { PyErr_Clear(); @@ -2853,6 +2855,15 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss return rv; newobj = PyObject_CallMethod(obj, "_asdict", NULL); if (newobj != NULL) { + if (!PyDict_Check(newobj)) { + PyErr_Format( + PyExc_TypeError, + "_asdict() must return a dict, not %.80s", + Py_TYPE(newobj)->tp_name + ); + Py_DECREF(newobj); + return -1; + } rv = encoder_listencode_dict(s, rval, newobj, indent_level); Py_DECREF(newobj); } diff --git a/lib/simplejson/encoder.py b/lib/simplejson/encoder.py index 7ea172e7..2f81cabc 100644 --- a/lib/simplejson/encoder.py +++ b/lib/simplejson/encoder.py @@ -520,7 +520,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, else: _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) if _asdict and callable(_asdict): - chunks = _iterencode_dict(_asdict(), + dct = _asdict() + if not isinstance(dct, dict): + raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) + chunks = _iterencode_dict(dct, _current_indent_level) elif _tuple_as_array and isinstance(value, tuple): chunks = _iterencode_list(value, _current_indent_level) @@ -641,7 +644,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, else: _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) if _asdict and callable(_asdict): - chunks = _iterencode_dict(_asdict(), + dct = _asdict() + if not isinstance(dct, dict): + raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) + chunks = _iterencode_dict(dct, _current_indent_level) elif _tuple_as_array and isinstance(value, tuple): chunks = _iterencode_list(value, _current_indent_level) @@ -686,8 +692,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, else: _asdict = _namedtuple_as_object and getattr(o, '_asdict', None) if _asdict and callable(_asdict): - for chunk in _iterencode_dict(_asdict(), - _current_indent_level): + dct = _asdict() + 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): yield chunk elif (_tuple_as_array and isinstance(o, tuple)): for chunk in _iterencode_list(o, _current_indent_level): diff --git a/lib/simplejson/tests/__init__.py b/lib/simplejson/tests/__init__.py index 25d3305b..79d1d170 100644 --- a/lib/simplejson/tests/__init__.py +++ b/lib/simplejson/tests/__init__.py @@ -7,6 +7,7 @@ import os class NoExtensionTestSuite(unittest.TestSuite): def run(self, result): import simplejson + simplejson._toggle_speedups(False) result = unittest.TestSuite.run(self, result) simplejson._toggle_speedups(True) @@ -15,16 +16,17 @@ class NoExtensionTestSuite(unittest.TestSuite): class TestMissingSpeedups(unittest.TestCase): def runTest(self): - if hasattr(sys, 'pypy_translation_info'): + if hasattr(sys, "pypy_translation_info"): "PyPy doesn't need speedups! :)" - elif hasattr(self, 'skipTest'): - self.skipTest('_speedups.so is missing!') + elif hasattr(self, "skipTest"): + self.skipTest("_speedups.so is missing!") -def additional_tests(suite=None): +def additional_tests(suite=None, project_dir=None): import simplejson import simplejson.encoder import simplejson.decoder + if suite is None: suite = unittest.TestSuite() try: @@ -36,39 +38,54 @@ def additional_tests(suite=None): raise for mod in (simplejson, simplejson.encoder, simplejson.decoder): suite.addTest(doctest.DocTestSuite(mod)) - suite.addTest(doctest.DocFileSuite('../../index.rst')) + if project_dir is not None: + suite.addTest( + doctest.DocFileSuite( + os.path.join(project_dir, "index.rst"), module_relative=False + ) + ) return suite -def all_tests_suite(): +def all_tests_suite(project_dir=None): def get_suite(): suite_names = [ - 'simplejson.tests.%s' % (os.path.splitext(f)[0],) + "simplejson.tests.%s" % (os.path.splitext(f)[0],) for f in os.listdir(os.path.dirname(__file__)) - if f.startswith('test_') and f.endswith('.py') + if f.startswith("test_") and f.endswith(".py") ] return additional_tests( - unittest.TestLoader().loadTestsFromNames(suite_names)) + suite=unittest.TestLoader().loadTestsFromNames(suite_names), + project_dir=project_dir, + ) + suite = get_suite() import simplejson + if simplejson._import_c_make_encoder() is None: suite.addTest(TestMissingSpeedups()) else: - suite = unittest.TestSuite([ - suite, - NoExtensionTestSuite([get_suite()]), - ]) + suite = unittest.TestSuite( + [ + suite, + NoExtensionTestSuite([get_suite()]), + ] + ) return suite -def main(): - runner = unittest.TextTestRunner(verbosity=1 + sys.argv.count('-v')) - suite = all_tests_suite() +def main(project_dir=None): + runner = unittest.TextTestRunner(verbosity=1 + sys.argv.count("-v")) + suite = all_tests_suite(project_dir=project_dir) raise SystemExit(not runner.run(suite).wasSuccessful()) -if __name__ == '__main__': +if __name__ == "__main__": import os import sys - sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) - main() + + project_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + ) + sys.path.insert(0, project_dir) + main(project_dir=project_dir) diff --git a/lib/simplejson/tests/_cibw_runner.py b/lib/simplejson/tests/_cibw_runner.py new file mode 100644 index 00000000..bffb70b8 --- /dev/null +++ b/lib/simplejson/tests/_cibw_runner.py @@ -0,0 +1,7 @@ +"""Internal module for running tests from cibuildwheel""" + +import sys +import simplejson.tests + +if __name__ == '__main__': + simplejson.tests.main(project_dir=sys.argv[1]) diff --git a/lib/simplejson/tests/test_namedtuple.py b/lib/simplejson/tests/test_namedtuple.py index 43878940..8035a5f5 100644 --- a/lib/simplejson/tests/test_namedtuple.py +++ b/lib/simplejson/tests/test_namedtuple.py @@ -3,6 +3,11 @@ import unittest import simplejson as json from simplejson.compat import StringIO +try: + from unittest import mock +except ImportError: + mock = None + try: from collections import namedtuple except ImportError: @@ -120,3 +125,25 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual( json.dumps(f({})), json.dumps(f(DeadDict()), namedtuple_as_object=True)) + + def test_asdict_does_not_return_dict(self): + if not mock: + if hasattr(unittest, "SkipTest"): + raise unittest.SkipTest("unittest.mock required") + else: + print("unittest.mock not available") + return + fake = mock.Mock() + self.assertTrue(hasattr(fake, '_asdict')) + self.assertTrue(callable(fake._asdict)) + self.assertFalse(isinstance(fake._asdict(), dict)) + # https://github.com/simplejson/simplejson/pull/284 + # when running under a debug build of CPython (COPTS=-UNDEBUG) + # a C assertion could fire due to an unchecked error of an PyDict + # API call on a non-dict internally in _speedups.c. Without a debug + # build of CPython this test likely passes either way despite the + # potential for internal data corruption. Getting it to crash in + # a debug build is not always easy either as it requires an + # assert(!PyErr_Occurred()) that could fire later on. + with self.assertRaises(TypeError): + json.dumps({23: fake}, namedtuple_as_object=True, for_json=False)