simplejson 4.1.2__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {simplejson-4.1.2 → simplejson-4.2.0}/CHANGES.txt +23 -0
  2. {simplejson-4.1.2/simplejson.egg-info → simplejson-4.2.0}/PKG-INFO +5 -5
  3. {simplejson-4.1.2 → simplejson-4.2.0}/README.rst +2 -2
  4. {simplejson-4.1.2 → simplejson-4.2.0}/conf.py +2 -2
  5. {simplejson-4.1.2 → simplejson-4.2.0}/index.rst +5 -1
  6. {simplejson-4.1.2 → simplejson-4.2.0}/setup.py +3 -3
  7. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/__init__.py +2 -2
  8. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/_speedups.c +101 -25
  9. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/_speedups_scan.h +5 -1
  10. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/encoder.py +26 -11
  11. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_bitsize_int_as_string.py +97 -0
  12. simplejson-4.2.0/simplejson/tests/test_custom_method_cycles.py +71 -0
  13. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_fail.py +26 -0
  14. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_scanstring.py +1 -0
  15. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_speedups.py +1 -8
  16. {simplejson-4.1.2 → simplejson-4.2.0/simplejson.egg-info}/PKG-INFO +5 -5
  17. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson.egg-info/SOURCES.txt +1 -0
  18. {simplejson-4.1.2 → simplejson-4.2.0}/LICENSE.txt +0 -0
  19. {simplejson-4.1.2 → simplejson-4.2.0}/MANIFEST.in +0 -0
  20. {simplejson-4.1.2 → simplejson-4.2.0}/pyproject.toml +0 -0
  21. {simplejson-4.1.2 → simplejson-4.2.0}/scripts/make_docs.py +0 -0
  22. {simplejson-4.1.2 → simplejson-4.2.0}/setup.cfg +0 -0
  23. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/compat.py +0 -0
  24. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/decoder.py +0 -0
  25. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/errors.py +0 -0
  26. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/ordered_dict.py +0 -0
  27. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/raw_json.py +0 -0
  28. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/scanner.py +0 -0
  29. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/__init__.py +0 -0
  30. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/_cibw_runner.py +0 -0
  31. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/_helpers.py +0 -0
  32. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_bigint_as_string.py +0 -0
  33. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_check_circular.py +0 -0
  34. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_decimal.py +0 -0
  35. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_decode.py +0 -0
  36. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_default.py +0 -0
  37. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_dump.py +0 -0
  38. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_encode_basestring_ascii.py +0 -0
  39. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_encode_for_html.py +0 -0
  40. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_errors.py +0 -0
  41. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_float.py +0 -0
  42. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_for_json.py +0 -0
  43. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_free_threading.py +0 -0
  44. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_indent.py +0 -0
  45. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_item_sort_key.py +0 -0
  46. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_iterable.py +0 -0
  47. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_namedtuple.py +0 -0
  48. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_pass1.py +0 -0
  49. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_pass2.py +0 -0
  50. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_pass3.py +0 -0
  51. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_raw_json.py +0 -0
  52. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_recursion.py +0 -0
  53. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_separators.py +0 -0
  54. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_str_subclass.py +0 -0
  55. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_subclass.py +0 -0
  56. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_subinterpreters.py +0 -0
  57. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_tool.py +0 -0
  58. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_tuple.py +0 -0
  59. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tests/test_unicode.py +0 -0
  60. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson/tool.py +0 -0
  61. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson.egg-info/dependency_links.txt +0 -0
  62. {simplejson-4.1.2 → simplejson-4.2.0}/simplejson.egg-info/top_level.txt +0 -0
  63. {simplejson-4.1.2 → simplejson-4.2.0}/tsan_stress_simplejson.py +0 -0
@@ -1,3 +1,26 @@
1
+ Version 4.2.0 released 2026-10-02
2
+
3
+ * Start building wheels for Python 3.15 and drop Python 3.8 support
4
+ via upgrade to cibuildwheel v4.2.0
5
+ https://github.com/simplejson/simplejson/pull/385
6
+ * Build and test riscv64 Linux wheels on native RISE runners
7
+ https://github.com/simplejson/simplejson/pull/389
8
+ * The C scanner now reports the same error message as the pure Python
9
+ decoder for an invalid first key in an object
10
+ https://github.com/simplejson/simplejson/pull/388
11
+ * ``int_as_string_bitcount`` now supports thresholds of 64 bits and above
12
+ (including 128-bit integers) in both the C and pure Python encoders;
13
+ the C encoder previously rejected them
14
+ https://github.com/simplejson/simplejson/pull/387
15
+ * Circular references introduced through ``for_json()`` or ``_asdict()``
16
+ now raise ``ValueError("Circular reference detected")`` instead of
17
+ recursing without bound
18
+ https://github.com/simplejson/simplejson/pull/390
19
+ * CI now tests Python 3.15 (including free-threaded 3.15t), PyPy 3.12,
20
+ CPython 3.14.8 debug builds, and uses current GitHub Actions and
21
+ cibuildwheel v4.2.1
22
+ https://github.com/simplejson/simplejson/pull/386
23
+
1
24
  Version 4.1.2 released 2026-08-26
2
25
 
3
26
  * Fix control character error position when content precedes the control char
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: Simple, fast, extensible JSON encoder/decoder for Python
5
5
  Home-page: https://github.com/simplejson/simplejson
6
6
  Author: Bob Ippolito
@@ -14,18 +14,18 @@ Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 2
15
15
  Classifier: Programming Language :: Python :: 2.7
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
17
  Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
25
  Classifier: Programming Language :: Python :: Implementation :: GraalPy
26
26
  Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
28
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
29
29
  License-File: LICENSE.txt
30
30
  Dynamic: author
31
31
  Dynamic: author-email
@@ -42,7 +42,7 @@ simplejson
42
42
  ----------
43
43
 
44
44
  simplejson is a simple, fast, complete, correct and extensible
45
- JSON <http://json.org> encoder and decoder for Python 3.8+
45
+ JSON <http://json.org> encoder and decoder for Python 3.9+
46
46
  with legacy support for Python 2.7. It is pure Python code
47
47
  with no dependencies, but includes an optional C extension
48
48
  for a serious speed boost.
@@ -53,7 +53,7 @@ https://simplejson.readthedocs.io/
53
53
  simplejson is the externally maintained development version of the
54
54
  json library included with Python (since 2.6). This version is tested
55
55
  with Python 3.14 (including free-threaded builds) and maintains
56
- backwards compatibility with Python 3.8+. A legacy Python 2.7 wheel
56
+ backwards compatibility with Python 3.9+. A legacy Python 2.7 wheel
57
57
  is also published.
58
58
 
59
59
  The encoder can be specialized to provide serialization in any kind of
@@ -2,7 +2,7 @@ simplejson
2
2
  ----------
3
3
 
4
4
  simplejson is a simple, fast, complete, correct and extensible
5
- JSON <http://json.org> encoder and decoder for Python 3.8+
5
+ JSON <http://json.org> encoder and decoder for Python 3.9+
6
6
  with legacy support for Python 2.7. It is pure Python code
7
7
  with no dependencies, but includes an optional C extension
8
8
  for a serious speed boost.
@@ -13,7 +13,7 @@ https://simplejson.readthedocs.io/
13
13
  simplejson is the externally maintained development version of the
14
14
  json library included with Python (since 2.6). This version is tested
15
15
  with Python 3.14 (including free-threaded builds) and maintains
16
- backwards compatibility with Python 3.8+. A legacy Python 2.7 wheel
16
+ backwards compatibility with Python 3.9+. A legacy Python 2.7 wheel
17
17
  is also published.
18
18
 
19
19
  The encoder can be specialized to provide serialization in any kind of
@@ -42,9 +42,9 @@ copyright = '2026, Bob Ippolito'
42
42
  # other places throughout the built documents.
43
43
  #
44
44
  # The short X.Y version.
45
- version = '4.1'
45
+ version = '4.2'
46
46
  # The full version, including alpha/beta/rc tags.
47
- release = '4.1.2'
47
+ release = '4.2.0'
48
48
 
49
49
  # There are two options for replacing |today|: either, you set today to some
50
50
  # non-false value, then it is used:
@@ -21,7 +21,7 @@ for a serious speed boost.
21
21
  :mod:`simplejson` exposes an API familiar to users of the standard library
22
22
  :mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
23
23
  version of the :mod:`json` library, but maintains
24
- compatibility with Python 3.8 through 3.14 (including free-threaded
24
+ compatibility with Python 3.9 through 3.15 (including free-threaded
25
25
  builds) as well as legacy Python 2.7.
26
26
 
27
27
  Development of simplejson happens on Github:
@@ -310,6 +310,10 @@ Basic Usage
310
310
  :class:`int` ``2**n`` and higher or lower than ``-2**n`` will be encoded as strings. This is to
311
311
  avoid the rounding that happens in Javascript otherwise. Note that this
312
312
  option loses type information, so use with extreme caution.
313
+ Thresholds of 64 bits and above are supported by both encoders, including
314
+ 128-bit integers. Very large thresholds do not allocate a power-of-two
315
+ boundary.
316
+
313
317
  See also *bigint_as_string* (which is equivalent to `int_as_string_bitcount=53`).
314
318
 
315
319
  .. note::
@@ -12,13 +12,13 @@ from distutils.errors import CCompilerError, DistutilsExecError, \
12
12
 
13
13
  IS_PYPY = hasattr(sys, 'pypy_translation_info')
14
14
  IS_GRAALPY = getattr(getattr(sys, "implementation", None), "name", None) == "graalpy"
15
- VERSION = '4.1.2'
15
+ VERSION = '4.2.0'
16
16
  DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python"
17
17
 
18
18
  with open('README.rst', 'r') as f:
19
19
  LONG_DESCRIPTION = f.read()
20
20
 
21
- PYTHON_REQUIRES = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*'
21
+ PYTHON_REQUIRES = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*'
22
22
 
23
23
  CLASSIFIERS = [
24
24
  'Development Status :: 5 - Production/Stable',
@@ -28,13 +28,13 @@ CLASSIFIERS = [
28
28
  'Programming Language :: Python :: 2',
29
29
  'Programming Language :: Python :: 2.7',
30
30
  'Programming Language :: Python :: 3',
31
- 'Programming Language :: Python :: 3.8',
32
31
  'Programming Language :: Python :: 3.9',
33
32
  'Programming Language :: Python :: 3.10',
34
33
  'Programming Language :: Python :: 3.11',
35
34
  'Programming Language :: Python :: 3.12',
36
35
  'Programming Language :: Python :: 3.13',
37
36
  'Programming Language :: Python :: 3.14',
37
+ 'Programming Language :: Python :: 3.15',
38
38
  'Programming Language :: Python :: Implementation :: CPython',
39
39
  'Programming Language :: Python :: Implementation :: GraalPy',
40
40
  'Programming Language :: Python :: Implementation :: PyPy',
@@ -5,7 +5,7 @@ interchange format.
5
5
  :mod:`simplejson` exposes an API familiar to users of the standard library
6
6
  :mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
7
7
  version of the :mod:`json` library contained in Python 2.6+, supporting
8
- Python 2.7 and Python 3.8+, and has significant performance advantages,
8
+ Python 2.7 and Python 3.9+, and has significant performance advantages,
9
9
  even without using the optional C extension for speedups.
10
10
 
11
11
  Encoding basic Python object hierarchies::
@@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON)::
118
118
 
119
119
  """
120
120
  from __future__ import absolute_import
121
- __version__ = '4.1.2'
121
+ __version__ = '4.2.0'
122
122
  __all__ = [
123
123
  'dump', 'dumps', 'load', 'loads',
124
124
  'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -301,6 +301,7 @@ typedef struct _PyEncoderObject {
301
301
  int iterable_as_array;
302
302
  PyObject *max_long_size;
303
303
  PyObject *min_long_size;
304
+ PyObject *large_int_bitcount;
304
305
  PyObject *item_sort_key;
305
306
  PyObject *item_sort_kw;
306
307
  int for_json;
@@ -327,6 +328,7 @@ typedef struct _PyEncoderObject {
327
328
  X(item_sort_key) \
328
329
  X(max_long_size) \
329
330
  X(min_long_size) \
331
+ X(large_int_bitcount) \
330
332
  X(Decimal)
331
333
 
332
334
  static PyMemberDef encoder_members[] = {
@@ -708,22 +710,46 @@ maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj)
708
710
  int ge, le;
709
711
  PyObject *quoted;
710
712
 
711
- /* int_as_string_bitcount is not set: fast path, return as-is. */
712
- if (s->max_long_size == Py_None || s->min_long_size == Py_None)
713
- return encoded;
714
-
715
- ge = PyObject_RichCompareBool(obj, s->max_long_size, Py_GE);
716
- if (ge < 0) {
717
- Py_DECREF(encoded);
718
- return NULL;
713
+ if (s->large_int_bitcount != NULL) {
714
+ size_t nbits;
715
+ PyObject *bits;
716
+ nbits = _PyLong_NumBits(obj);
717
+ if (nbits == (size_t)-1 && PyErr_Occurred()) {
718
+ Py_DECREF(encoded);
719
+ return NULL;
720
+ }
721
+ bits = PyLong_FromSize_t(nbits);
722
+ if (bits == NULL) {
723
+ Py_DECREF(encoded);
724
+ return NULL;
725
+ }
726
+ ge = PyObject_RichCompareBool(bits, s->large_int_bitcount, Py_GT);
727
+ Py_DECREF(bits);
728
+ if (ge < 0) {
729
+ Py_DECREF(encoded);
730
+ return NULL;
731
+ }
732
+ if (!ge)
733
+ return encoded;
719
734
  }
720
- le = PyObject_RichCompareBool(obj, s->min_long_size, Py_LE);
721
- if (le < 0) {
722
- Py_DECREF(encoded);
723
- return NULL;
735
+ else {
736
+ /* int_as_string_bitcount is not set: fast path, return as-is. */
737
+ if (s->max_long_size == Py_None || s->min_long_size == Py_None)
738
+ return encoded;
739
+
740
+ ge = PyObject_RichCompareBool(obj, s->max_long_size, Py_GE);
741
+ if (ge < 0) {
742
+ Py_DECREF(encoded);
743
+ return NULL;
744
+ }
745
+ le = PyObject_RichCompareBool(obj, s->min_long_size, Py_LE);
746
+ if (le < 0) {
747
+ Py_DECREF(encoded);
748
+ return NULL;
749
+ }
750
+ if (!(ge || le))
751
+ return encoded;
724
752
  }
725
- if (!(ge || le))
726
- return encoded;
727
753
 
728
754
  #if PY_MAJOR_VERSION >= 3
729
755
  quoted = PyUnicode_FromFormat("\"%U\"", encoded);
@@ -2653,10 +2679,11 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
2653
2679
  goto bail;
2654
2680
  if (PyInt_Check(int_as_string_bitcount) || PyLong_Check(int_as_string_bitcount)) {
2655
2681
  static const unsigned long long_long_bitsize = sizeof(long long) * CHAR_BIT;
2656
- long int_as_string_bitcount_val = PyLong_AsLong(int_as_string_bitcount);
2682
+ int overflow = 0;
2683
+ long int_as_string_bitcount_val = PyLong_AsLongAndOverflow(int_as_string_bitcount, &overflow);
2657
2684
  if (int_as_string_bitcount_val == -1 && PyErr_Occurred())
2658
2685
  goto bail;
2659
- if (int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < (long)long_long_bitsize) {
2686
+ if (!overflow && int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < (long)long_long_bitsize) {
2660
2687
  int n = (int)int_as_string_bitcount_val;
2661
2688
  /* Compute 2^n as unsigned (well-defined for n < 64) and
2662
2689
  * -(2^n) as signed without UB. Naive "-1LL << n" is a
@@ -2672,10 +2699,19 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
2672
2699
  goto bail;
2673
2700
  }
2674
2701
  }
2702
+ else if (overflow > 0 || int_as_string_bitcount_val > 0) {
2703
+ /* Compare magnitudes without allocating a 2**n boundary. */
2704
+ s->large_int_bitcount = PyNumber_Long(int_as_string_bitcount);
2705
+ if (s->large_int_bitcount == NULL)
2706
+ goto bail;
2707
+ Py_INCREF(Py_None);
2708
+ s->max_long_size = Py_None;
2709
+ Py_INCREF(Py_None);
2710
+ s->min_long_size = Py_None;
2711
+ }
2675
2712
  else {
2676
- PyErr_Format(PyExc_TypeError,
2677
- "int_as_string_bitcount (%ld) must be greater than 0 and less than the number of bits of a `long long` type (%lu bits)",
2678
- int_as_string_bitcount_val, long_long_bitsize);
2713
+ PyErr_SetString(PyExc_TypeError,
2714
+ "int_as_string_bitcount must be a positive integer");
2679
2715
  goto bail;
2680
2716
  }
2681
2717
  }
@@ -2979,7 +3015,8 @@ encoder_markers_pop(PyEncoderObject *s, PyObject *ident)
2979
3015
 
2980
3016
  /* Helper for the for_json / _asdict paths in encoder_listencode_obj.
2981
3017
  * Steals the reference to `newobj` (returned by _call_json_method),
2982
- * handles recursion-depth tracking, and dispatches to the right
3018
+ * tracks the original object's circular-reference marker and recursion
3019
+ * depth, and dispatches to the right
2983
3020
  * sub-encoder:
2984
3021
  * - if as_dict is 0, encodes newobj as a generic JSON value via
2985
3022
  * encoder_listencode_obj (for_json contract: return any JSON-
@@ -2990,14 +3027,20 @@ encoder_markers_pop(PyEncoderObject *s, PyObject *ident)
2990
3027
  * Cleans up on every exit path. */
2991
3028
  static int
2992
3029
  encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
2993
- PyObject *newobj, Py_ssize_t indent_level,
3030
+ PyObject *obj, PyObject *newobj, Py_ssize_t indent_level,
2994
3031
  int as_dict)
2995
3032
  {
2996
3033
  int rv;
3034
+ PyObject *ident = NULL;
2997
3035
  if (newobj == NULL)
2998
3036
  return -1;
3037
+ if (encoder_markers_push(s, obj, &ident)) {
3038
+ Py_DECREF(newobj);
3039
+ return -1;
3040
+ }
2999
3041
  if (Py_EnterRecursiveCall(" while encoding a JSON object")) {
3000
3042
  Py_DECREF(newobj);
3043
+ Py_XDECREF(ident);
3001
3044
  return -1;
3002
3045
  }
3003
3046
  if (as_dict) {
@@ -3014,6 +3057,12 @@ encoder_steal_encode(PyEncoderObject *s, JSON_Accu *rval,
3014
3057
  }
3015
3058
  Py_DECREF(newobj);
3016
3059
  Py_LeaveRecursiveCall();
3060
+ if (rv == 0) {
3061
+ if (encoder_markers_pop(s, ident) < 0)
3062
+ rv = -1;
3063
+ } else {
3064
+ Py_XDECREF(ident);
3065
+ }
3017
3066
  return rv;
3018
3067
  }
3019
3068
 
@@ -3121,12 +3170,39 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3121
3170
  rv = _steal_accumulate(state, rval, cstr);
3122
3171
  }
3123
3172
  else if (PyInt_Check(obj) || PyLong_Check(obj)) {
3124
- PyObject *encoded = encoder_long_to_str(obj);
3173
+ PyObject *integer, *encoded;
3174
+ /* Large thresholds inspect the same normalized value we serialize. */
3175
+ if (s->large_int_bitcount != NULL) {
3176
+ #if PY_MAJOR_VERSION < 3
3177
+ /* Match int(value) in the Python encoder before widening for
3178
+ * _PyLong_NumBits: long(value) would invoke __long__ instead. */
3179
+ PyObject *normalized = PyNumber_Int(obj);
3180
+ if (normalized == NULL)
3181
+ return -1;
3182
+ /* PyNumber_Int may retain a long subclass. Copy its payload
3183
+ * instead of invoking a second conversion hook. */
3184
+ if (PyInt_Check(normalized))
3185
+ integer = PyLong_FromLong(PyInt_AS_LONG(normalized));
3186
+ else
3187
+ integer = PyLong_Type.tp_as_number->nb_long(normalized);
3188
+ Py_DECREF(normalized);
3189
+ #else
3190
+ integer = PyNumber_Long(obj);
3191
+ #endif
3192
+ }
3193
+ else {
3194
+ integer = obj;
3195
+ Py_INCREF(integer);
3196
+ }
3197
+ if (integer == NULL)
3198
+ return -1;
3199
+ encoded = encoder_long_to_str(integer);
3125
3200
  if (encoded != NULL) {
3126
- encoded = maybe_quote_bigint(s, encoded, obj);
3201
+ encoded = maybe_quote_bigint(s, encoded, integer);
3127
3202
  if (encoded != NULL)
3128
3203
  rv = _steal_accumulate(state, rval, encoded);
3129
3204
  }
3205
+ Py_DECREF(integer);
3130
3206
  }
3131
3207
  else if (PyFloat_Check(obj)) {
3132
3208
  PyObject *encoded = encoder_encode_float(s, obj);
@@ -3134,10 +3210,10 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss
3134
3210
  rv = _steal_accumulate(state, rval, encoded);
3135
3211
  }
3136
3212
  else if (s->for_json && _call_json_method(obj, state->JSON_attr_for_json, &newobj)) {
3137
- rv = encoder_steal_encode(s, rval, newobj, indent_level, /*as_dict=*/0);
3213
+ rv = encoder_steal_encode(s, rval, obj, newobj, indent_level, /*as_dict=*/0);
3138
3214
  }
3139
3215
  else if (s->namedtuple_as_object && _call_json_method(obj, state->JSON_attr_asdict, &newobj)) {
3140
- rv = encoder_steal_encode(s, rval, newobj, indent_level, /*as_dict=*/1);
3216
+ rv = encoder_steal_encode(s, rval, obj, newobj, indent_level, /*as_dict=*/1);
3141
3217
  }
3142
3218
  else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) {
3143
3219
  if (Py_EnterRecursiveCall(" while encoding a JSON object"))
@@ -210,7 +210,11 @@ JSON_SCAN_FN(_parse_object)(PyScannerObject *s, PyObject *pystr,
210
210
 
211
211
  /* read key */
212
212
  if (JSON_SCAN_READ(idx) != '"') {
213
- raise_errmsg(state, ERR_OBJECT_PROPERTY, pystr, idx);
213
+ /* '}' is still legal where the first key would go. */
214
+ raise_errmsg(state,
215
+ did_parse ? ERR_OBJECT_PROPERTY
216
+ : ERR_OBJECT_PROPERTY_FIRST,
217
+ pystr, idx);
214
218
  goto bail;
215
219
  }
216
220
  key = JSON_SCAN_SCANSTRING_CALL(idx + 1, &next_idx);
@@ -516,13 +516,24 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
516
516
  value = int(value)
517
517
  if (
518
518
  skip_quoting or
519
- (-1 << _int_as_string_bitcount)
520
- < value <
521
- (1 << _int_as_string_bitcount)
519
+ value.bit_length() <= _int_as_string_bitcount
522
520
  ):
523
521
  return str(value)
524
522
  return '"' + str(value) + '"'
525
523
 
524
+ def _iterencode_method(obj, result, _current_indent_level, encode):
525
+ if markers is not None:
526
+ markerid = id(obj)
527
+ if markerid in markers:
528
+ raise ValueError("Circular reference detected")
529
+ markers[markerid] = obj
530
+ try:
531
+ for chunk in encode(result, _current_indent_level):
532
+ yield chunk
533
+ finally:
534
+ if markers is not None:
535
+ del markers[markerid]
536
+
526
537
  def _iterencode_list(lst, _current_indent_level):
527
538
  if not lst:
528
539
  yield '[]'
@@ -570,15 +581,16 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
570
581
  yield buf
571
582
  for_json = _for_json and call_method(value, 'for_json')
572
583
  if for_json:
573
- chunks = _iterencode(for_json[0], _current_indent_level)
584
+ chunks = _iterencode_method(value, for_json[0],
585
+ _current_indent_level, _iterencode)
574
586
  else:
575
587
  _asdict = _namedtuple_as_object and call_method(value, '_asdict')
576
588
  if _asdict:
577
589
  dct = _asdict[0]
578
590
  if not isinstance(dct, dict):
579
591
  raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,))
580
- chunks = _iterencode_dict(dct,
581
- _current_indent_level)
592
+ chunks = _iterencode_method(value, dct,
593
+ _current_indent_level, _iterencode_dict)
582
594
  elif isinstance(value, list):
583
595
  chunks = _iterencode_list(value, _current_indent_level)
584
596
  elif _tuple_as_array and isinstance(value, tuple):
@@ -701,15 +713,16 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
701
713
  else:
702
714
  for_json = _for_json and call_method(value, 'for_json')
703
715
  if for_json:
704
- chunks = _iterencode(for_json[0], _current_indent_level)
716
+ chunks = _iterencode_method(value, for_json[0],
717
+ _current_indent_level, _iterencode)
705
718
  else:
706
719
  _asdict = _namedtuple_as_object and call_method(value, '_asdict')
707
720
  if _asdict:
708
721
  dct = _asdict[0]
709
722
  if not isinstance(dct, dict):
710
723
  raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,))
711
- chunks = _iterencode_dict(dct,
712
- _current_indent_level)
724
+ chunks = _iterencode_method(value, dct,
725
+ _current_indent_level, _iterencode_dict)
713
726
  elif isinstance(value, list):
714
727
  chunks = _iterencode_list(value, _current_indent_level)
715
728
  elif _tuple_as_array and isinstance(value, tuple):
@@ -753,7 +766,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
753
766
  else:
754
767
  for_json = _for_json and call_method(o, 'for_json')
755
768
  if for_json:
756
- for chunk in _iterencode(for_json[0], _current_indent_level):
769
+ for chunk in _iterencode_method(o, for_json[0],
770
+ _current_indent_level, _iterencode):
757
771
  yield chunk
758
772
  else:
759
773
  _asdict = _namedtuple_as_object and call_method(o, '_asdict')
@@ -761,7 +775,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
761
775
  dct = _asdict[0]
762
776
  if not isinstance(dct, dict):
763
777
  raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,))
764
- for chunk in _iterencode_dict(dct, _current_indent_level):
778
+ for chunk in _iterencode_method(o, dct,
779
+ _current_indent_level, _iterencode_dict):
765
780
  yield chunk
766
781
  elif isinstance(o, list):
767
782
  for chunk in _iterencode_list(o, _current_indent_level):
@@ -1,6 +1,7 @@
1
1
  from unittest import TestCase
2
2
 
3
3
  import simplejson as json
4
+ from simplejson.compat import PY3, integer_types, long_type
4
5
 
5
6
 
6
7
  class TestBitSizeIntAsString(TestCase):
@@ -118,3 +119,99 @@ class TestBitSizeIntAsString(TestCase):
118
119
  str(v),
119
120
  json.loads(json.dumps(v, int_as_string_bitcount=n)),
120
121
  "n=%d v=%d should be stringified" % (n, v))
122
+
123
+
124
+ def test_large_bitcounts(self):
125
+ for n in (64, 65, 127, 128, 256, 1024):
126
+ boundary = 1 << n
127
+ values = [0, boundary - 1, boundary, boundary + 1,
128
+ -boundary + 1, -boundary, -boundary - 1]
129
+ expected = [0, boundary - 1, str(boundary), str(boundary + 1),
130
+ -boundary + 1, str(-boundary), str(-boundary - 1)]
131
+ for indent in (None, 2):
132
+ self.assertEqual(expected, json.loads(json.dumps(
133
+ values, int_as_string_bitcount=n, indent=indent)))
134
+ self.assertEqual({'value': expected}, json.loads(json.dumps(
135
+ {'value': values}, int_as_string_bitcount=n, indent=indent)))
136
+
137
+ def test_huge_bitcount_does_not_build_boundary(self):
138
+ # A threshold can exceed native integer sizes without allocating 2**n.
139
+ for n in (2 ** 32 + 31, 2 ** 100):
140
+ self.assertEqual('[0, 1, -1]', json.dumps(
141
+ [0, 1, -1], int_as_string_bitcount=n))
142
+
143
+ def test_large_bitcount_normalizes_subclass_once(self):
144
+ for integer_type in integer_types:
145
+ class ChangingInt(integer_type):
146
+ calls = 0
147
+
148
+ def __int__(self):
149
+ self.calls += 1
150
+ return normalized if self.calls == 1 else 0
151
+
152
+ def __long__(self):
153
+ raise AssertionError('must normalize through __int__')
154
+
155
+ for normalized in (1 << 64, -(1 << 64), 1):
156
+ expected = str(normalized)
157
+ if abs(normalized) >= 1 << 64:
158
+ expected = '"' + expected + '"'
159
+ for indent in (None, 2):
160
+ value = ChangingInt(1)
161
+ self.assertEqual(expected, json.dumps(
162
+ value, int_as_string_bitcount=64, indent=indent))
163
+ self.assertEqual(1, value.calls)
164
+
165
+ def test_large_bitcount_normalization_error_propagates(self):
166
+ for integer_type in integer_types:
167
+ class BadInt(integer_type):
168
+ def __int__(self):
169
+ raise RuntimeError('normalization bomb')
170
+
171
+ for indent in (None, 2):
172
+ self.assertRaises(
173
+ RuntimeError, json.dumps, BadInt(1),
174
+ int_as_string_bitcount=64, indent=indent)
175
+
176
+ def _assert_returned_long_is_not_converted(self, returned_type):
177
+ for integer_type in integer_types:
178
+ class OriginalInt(integer_type):
179
+ calls = 0
180
+
181
+ def __int__(self):
182
+ self.calls += 1
183
+ return returned
184
+
185
+ def __long__(self):
186
+ return long_type(7)
187
+
188
+ for normalized in (7, 1 << 64, -(1 << 64)):
189
+ expected = str(normalized)
190
+ if abs(normalized) >= 1 << 64:
191
+ expected = '"' + expected + '"'
192
+ for indent in (None, 2):
193
+ returned = returned_type(normalized)
194
+ value = OriginalInt(7)
195
+ self.assertEqual(expected, json.dumps(
196
+ value, int_as_string_bitcount=64, indent=indent))
197
+ self.assertEqual(1, value.calls)
198
+
199
+ def test_large_bitcount_returned_long_value_preserved(self):
200
+ if PY3:
201
+ self.skipTest('Python 2 int() can return a long subclass')
202
+
203
+ class ReturnedLong(long_type):
204
+ def __long__(self):
205
+ return 1 << 64
206
+
207
+ self._assert_returned_long_is_not_converted(ReturnedLong)
208
+
209
+ def test_large_bitcount_returned_long_hook_not_called(self):
210
+ if PY3:
211
+ self.skipTest('Python 2 int() can return a long subclass')
212
+
213
+ class ReturnedLong(long_type):
214
+ def __long__(self):
215
+ raise RuntimeError('unexpected second conversion')
216
+
217
+ self._assert_returned_long_is_not_converted(ReturnedLong)
@@ -0,0 +1,71 @@
1
+ import sys
2
+ from unittest import TestCase, skipIf
3
+
4
+ import simplejson as json
5
+
6
+
7
+ class ForJson:
8
+ def __init__(self, value=None):
9
+ self.value = value
10
+
11
+ def for_json(self):
12
+ return self.value
13
+
14
+
15
+ class AsDict:
16
+ def _asdict(self):
17
+ return {'self': self}
18
+
19
+
20
+ class TestCustomMethodCycles(TestCase):
21
+ def assert_circular(self, value):
22
+ with self.assertRaises(ValueError) as exc:
23
+ json.dumps(value, for_json=True)
24
+ self.assertEqual(str(exc.exception), 'Circular reference detected')
25
+
26
+ def test_direct_for_json_cycle(self):
27
+ value = ForJson()
28
+ value.value = value
29
+ self.assert_circular(value)
30
+
31
+ def test_indirect_for_json_cycle(self):
32
+ first = ForJson()
33
+ second = ForJson(first)
34
+ first.value = second
35
+ self.assert_circular(first)
36
+
37
+ def test_for_json_cycle_in_list(self):
38
+ value = ForJson()
39
+ value.value = value
40
+ self.assert_circular([value])
41
+
42
+ def test_for_json_cycle_in_dict(self):
43
+ value = ForJson()
44
+ value.value = value
45
+ self.assert_circular({'value': value})
46
+
47
+ def test_asdict_cycle(self):
48
+ self.assert_circular(AsDict())
49
+
50
+ def test_asdict_cycle_in_list(self):
51
+ self.assert_circular([AsDict()])
52
+
53
+ def test_asdict_cycle_in_dict(self):
54
+ self.assert_circular({'value': AsDict()})
55
+
56
+ def test_repeated_reference_is_not_a_cycle(self):
57
+ value = ForJson({'x': 1})
58
+ self.assertEqual(
59
+ json.loads(json.dumps([value, value], for_json=True)),
60
+ [{'x': 1}, {'x': 1}])
61
+
62
+ def test_acyclic_chain(self):
63
+ self.assertEqual(json.loads(json.dumps(ForJson(ForJson(1)), for_json=True)), 1)
64
+
65
+ @skipIf(sys.platform == 'emscripten',
66
+ 'Pyodide cannot recover from unbounded recursion')
67
+ def test_circular_checks_can_be_disabled(self):
68
+ value = ForJson()
69
+ value.value = value
70
+ with self.assertRaises(RuntimeError):
71
+ json.dumps(value, for_json=True, check_circular=False)
@@ -135,6 +135,32 @@ class TestFail(TestCase):
135
135
  else:
136
136
  self.fail("Unexpected success parsing '[,]'")
137
137
 
138
+ def test_object_property_error_message(self):
139
+ # '}' is legal where the first key would go, but not after a comma.
140
+ first = "Expecting property name enclosed in double quotes or '}'"
141
+ later = 'Expecting property name enclosed in double quotes'
142
+ test_cases = [
143
+ ('{:1}', first, 1),
144
+ ('{,}', first, 1),
145
+ ('{unquoted_key: "keys must be quoted"}', first, 1),
146
+ ('{"spam":42,:1}', later, 11),
147
+ ('{"spam":42,unquoted_key: 1}', later, 11),
148
+ ]
149
+ for data, msg, idx in test_cases:
150
+ try:
151
+ json.loads(data)
152
+ except json.JSONDecodeError:
153
+ e = sys.exc_info()[1]
154
+ self.assertEqual(
155
+ e.msg, msg, "%r != %r for %r" % (e.msg, msg, data))
156
+ self.assertEqual(
157
+ e.pos, idx, "pos %r != %r for %r" % (e.pos, idx, data))
158
+ except Exception:
159
+ e = sys.exc_info()[1]
160
+ self.fail("Unexpected exception raised %r %s" % (e, e))
161
+ else:
162
+ self.fail("Unexpected success parsing %r" % (data,))
163
+
138
164
  def test_truncated_input(self):
139
165
  test_cases = [
140
166
  ('', 'Expecting value', 0),
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  import sys
2
3
  from unittest import TestCase
3
4
 
@@ -4,7 +4,7 @@ from unittest import TestCase
4
4
 
5
5
  import simplejson
6
6
  from simplejson import encoder, decoder, scanner
7
- from simplejson.compat import PY3, long_type, b
7
+ from simplejson.compat import PY3, b
8
8
  from simplejson.tests._helpers import has_speedups, skip_if_speedups_missing
9
9
 
10
10
 
@@ -82,13 +82,6 @@ class TestEncode(TestCase):
82
82
  self.assertRaises(ZeroDivisionError, test, 'ignore_nan')
83
83
  self.assertRaises(ZeroDivisionError, test, 'iterable_as_array')
84
84
 
85
- @skip_if_speedups_missing
86
- def test_int_as_string_bitcount_overflow(self):
87
- long_count = long_type(2)**32+31
88
- def test():
89
- encoder.JSONEncoder(int_as_string_bitcount=long_count).encode(0)
90
- self.assertRaises((TypeError, OverflowError), test)
91
-
92
85
  if PY3:
93
86
  @skip_if_speedups_missing
94
87
  def test_bad_encoding(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: Simple, fast, extensible JSON encoder/decoder for Python
5
5
  Home-page: https://github.com/simplejson/simplejson
6
6
  Author: Bob Ippolito
@@ -14,18 +14,18 @@ Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 2
15
15
  Classifier: Programming Language :: Python :: 2.7
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
17
  Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
25
25
  Classifier: Programming Language :: Python :: Implementation :: GraalPy
26
26
  Classifier: Programming Language :: Python :: Implementation :: PyPy
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
- Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
28
+ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
29
29
  License-File: LICENSE.txt
30
30
  Dynamic: author
31
31
  Dynamic: author-email
@@ -42,7 +42,7 @@ simplejson
42
42
  ----------
43
43
 
44
44
  simplejson is a simple, fast, complete, correct and extensible
45
- JSON <http://json.org> encoder and decoder for Python 3.8+
45
+ JSON <http://json.org> encoder and decoder for Python 3.9+
46
46
  with legacy support for Python 2.7. It is pure Python code
47
47
  with no dependencies, but includes an optional C extension
48
48
  for a serious speed boost.
@@ -53,7 +53,7 @@ https://simplejson.readthedocs.io/
53
53
  simplejson is the externally maintained development version of the
54
54
  json library included with Python (since 2.6). This version is tested
55
55
  with Python 3.14 (including free-threaded builds) and maintains
56
- backwards compatibility with Python 3.8+. A legacy Python 2.7 wheel
56
+ backwards compatibility with Python 3.9+. A legacy Python 2.7 wheel
57
57
  is also published.
58
58
 
59
59
  The encoder can be specialized to provide serialization in any kind of
@@ -29,6 +29,7 @@ simplejson/tests/_helpers.py
29
29
  simplejson/tests/test_bigint_as_string.py
30
30
  simplejson/tests/test_bitsize_int_as_string.py
31
31
  simplejson/tests/test_check_circular.py
32
+ simplejson/tests/test_custom_method_cycles.py
32
33
  simplejson/tests/test_decimal.py
33
34
  simplejson/tests/test_decode.py
34
35
  simplejson/tests/test_default.py
File without changes
File without changes
File without changes
File without changes