simplejson 4.0.1__tar.gz → 4.1.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 (62) hide show
  1. {simplejson-4.0.1 → simplejson-4.1.0}/CHANGES.txt +19 -0
  2. {simplejson-4.0.1/simplejson.egg-info → simplejson-4.1.0}/PKG-INFO +1 -1
  3. {simplejson-4.0.1 → simplejson-4.1.0}/conf.py +2 -2
  4. {simplejson-4.0.1 → simplejson-4.1.0}/setup.py +1 -1
  5. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/__init__.py +1 -1
  6. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/_speedups.c +265 -32
  7. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/encoder.py +1 -1
  8. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_errors.py +3 -4
  9. {simplejson-4.0.1 → simplejson-4.1.0/simplejson.egg-info}/PKG-INFO +1 -1
  10. {simplejson-4.0.1 → simplejson-4.1.0}/LICENSE.txt +0 -0
  11. {simplejson-4.0.1 → simplejson-4.1.0}/MANIFEST.in +0 -0
  12. {simplejson-4.0.1 → simplejson-4.1.0}/README.rst +0 -0
  13. {simplejson-4.0.1 → simplejson-4.1.0}/index.rst +0 -0
  14. {simplejson-4.0.1 → simplejson-4.1.0}/pyproject.toml +0 -0
  15. {simplejson-4.0.1 → simplejson-4.1.0}/scripts/make_docs.py +0 -0
  16. {simplejson-4.0.1 → simplejson-4.1.0}/setup.cfg +0 -0
  17. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/_speedups_scan.h +0 -0
  18. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/compat.py +0 -0
  19. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/decoder.py +0 -0
  20. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/errors.py +0 -0
  21. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/ordered_dict.py +0 -0
  22. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/raw_json.py +0 -0
  23. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/scanner.py +0 -0
  24. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/__init__.py +0 -0
  25. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/_cibw_runner.py +0 -0
  26. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/_helpers.py +0 -0
  27. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_bigint_as_string.py +0 -0
  28. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_bitsize_int_as_string.py +0 -0
  29. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_check_circular.py +0 -0
  30. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_decimal.py +0 -0
  31. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_decode.py +0 -0
  32. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_default.py +0 -0
  33. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_dump.py +0 -0
  34. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_encode_basestring_ascii.py +0 -0
  35. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_encode_for_html.py +0 -0
  36. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_fail.py +0 -0
  37. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_float.py +0 -0
  38. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_for_json.py +0 -0
  39. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_free_threading.py +0 -0
  40. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_indent.py +0 -0
  41. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_item_sort_key.py +0 -0
  42. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_iterable.py +0 -0
  43. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_namedtuple.py +0 -0
  44. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass1.py +0 -0
  45. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass2.py +0 -0
  46. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass3.py +0 -0
  47. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_raw_json.py +0 -0
  48. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_recursion.py +0 -0
  49. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_scanstring.py +0 -0
  50. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_separators.py +0 -0
  51. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_speedups.py +0 -0
  52. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_str_subclass.py +0 -0
  53. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_subclass.py +0 -0
  54. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_subinterpreters.py +0 -0
  55. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_tool.py +0 -0
  56. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_tuple.py +0 -0
  57. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_unicode.py +0 -0
  58. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tool.py +0 -0
  59. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/SOURCES.txt +0 -0
  60. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/dependency_links.txt +0 -0
  61. {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/top_level.txt +0 -0
  62. {simplejson-4.0.1 → simplejson-4.1.0}/tsan_stress_simplejson.py +0 -0
@@ -1,3 +1,22 @@
1
+ Version 4.1.0 released 2026-04-22
2
+
3
+ * The C extension now accelerates encoding when ``indent=`` is set.
4
+ Previously the encoder fell back to the pure-Python implementation
5
+ whenever a non-None ``indent`` was passed; now the C encoder emits
6
+ the newline-plus-indent prefix, the level-aware item separator, and
7
+ the closing indent directly. A representative nested-dict workload
8
+ benchmarks about 4-5x faster end-to-end, and the ``indent=0`` and
9
+ empty-container edge cases continue to match the Python output
10
+ byte-for-byte.
11
+
12
+ * The C extension now emits PEP 678 ``exc.add_note()`` annotations on
13
+ serialization failures, matching the pure-Python encoder. A chained
14
+ error on ``{'a': [1, object(), 3]}`` produces the same three notes
15
+ (``when serializing object object``, ``when serializing list item 1``,
16
+ ``when serializing dict item 'a'``) whether the speedups are loaded
17
+ or not, so the add_note assertions in ``test_errors.py`` no longer
18
+ need ``indent=2`` to force the Python path.
19
+
1
20
  Version 4.0.1 released 2026-04-18
2
21
 
3
22
  * Skip uploading Pyodide/wasm wheels to PyPI, which rejects them with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.0.1
3
+ Version: 4.1.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
@@ -42,9 +42,9 @@ copyright = '2025, Bob Ippolito'
42
42
  # other places throughout the built documents.
43
43
  #
44
44
  # The short X.Y version.
45
- version = '4.0'
45
+ version = '4.1'
46
46
  # The full version, including alpha/beta/rc tags.
47
- release = '4.0.1'
47
+ release = '4.1.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:
@@ -12,7 +12,7 @@ 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.0.1'
15
+ VERSION = '4.1.0'
16
16
  DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python"
17
17
 
18
18
  with open('README.rst', 'r') as f:
@@ -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.0.1'
121
+ __version__ = '4.1.0'
122
122
  __all__ = [
123
123
  'dump', 'dumps', 'load', 'loads',
124
124
  'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -126,6 +126,7 @@ typedef struct {
126
126
  PyObject *JSON_open_array;
127
127
  PyObject *JSON_close_array;
128
128
  PyObject *JSON_empty_array;
129
+ PyObject *JSON_newline; /* "\n", prepended before each indent */
129
130
  PyObject *JSON_sortargs;
130
131
  PyObject *JSON_itemgetter0;
131
132
  /* Interned attribute-name strings used in hot paths. Caching them
@@ -136,6 +137,7 @@ typedef struct {
136
137
  PyObject *JSON_attr_asdict; /* "_asdict" */
137
138
  PyObject *JSON_attr_sort; /* "sort" */
138
139
  PyObject *JSON_attr_encoded_json; /* "encoded_json" */
140
+ PyObject *JSON_attr_add_note; /* "add_note" (PEP 678, 3.11+) */
139
141
  PyObject *RawJSONType;
140
142
  PyObject *JSONDecodeError;
141
143
  } _speedups_state;
@@ -428,6 +430,13 @@ encoder_long_to_str(PyObject *obj);
428
430
  static PyObject *
429
431
  encoder_encode_float(PyEncoderObject *s, PyObject *obj);
430
432
  static int
433
+ encoder_accumulate_newline_indent(PyEncoderObject *s, _speedups_state *state,
434
+ JSON_Accu *rval, Py_ssize_t indent_level);
435
+ #if PY_VERSION_HEX >= 0x030B0000
436
+ static void
437
+ encoder_annotate_exception(_speedups_state *state, const char *format, ...);
438
+ #endif
439
+ static int
431
440
  init_speedups_state(_speedups_state *state, PyObject *module);
432
441
  static PyObject *
433
442
  import_dependency(const char *module_name, const char *attr_name);
@@ -2121,11 +2130,13 @@ py_encode_basestring(PyObject* self UNUSED, PyObject *pystr)
2121
2130
  /* Return a JSON representation of a Python string (ensure_ascii=False) */
2122
2131
  /* METH_O */
2123
2132
  if (PyBytes_Check(pystr)) {
2124
- PyObject *uni = PyUnicode_DecodeUTF8(
2133
+ PyObject *uni;
2134
+ PyObject *rval;
2135
+ uni = PyUnicode_DecodeUTF8(
2125
2136
  PyBytes_AS_STRING(pystr), PyBytes_GET_SIZE(pystr), NULL);
2126
2137
  if (uni == NULL)
2127
2138
  return NULL;
2128
- PyObject *rval = escape_unicode_noascii(uni);
2139
+ rval = escape_unicode_noascii(uni);
2129
2140
  Py_DECREF(uni);
2130
2141
  return rval;
2131
2142
  }
@@ -2994,12 +3005,29 @@ encoder_listencode_default(PyEncoderObject *s, JSON_Accu *rval,
2994
3005
  }
2995
3006
  newobj = PyObject_CallOneArg(s->defaultfn, obj);
2996
3007
  if (newobj == NULL) {
3008
+ #if PY_VERSION_HEX >= 0x030B0000
3009
+ /* Annotate before unwinding; the "when serializing X object"
3010
+ * note uses the original obj's type name, matching the Python
3011
+ * encoder which binds type(o).__name__ before `o = default(o)`
3012
+ * runs. */
3013
+ encoder_annotate_exception(state,
3014
+ "when serializing %s object", Py_TYPE(obj)->tp_name);
3015
+ #endif
2997
3016
  Py_LeaveRecursiveCall();
2998
3017
  Py_XDECREF(ident);
2999
3018
  return -1;
3000
3019
  }
3001
3020
  rv = encoder_listencode_obj(s, rval, newobj, indent_level);
3002
3021
  Py_LeaveRecursiveCall();
3022
+ if (rv) {
3023
+ #if PY_VERSION_HEX >= 0x030B0000
3024
+ /* default() succeeded but encoding its return value failed; in
3025
+ * the Python encoder `o` has been rebound to newobj at this
3026
+ * point, so the note reflects that type. */
3027
+ encoder_annotate_exception(state,
3028
+ "when serializing %s object", Py_TYPE(newobj)->tp_name);
3029
+ #endif
3030
+ }
3003
3031
  Py_DECREF(newobj);
3004
3032
  if (rv == 0) {
3005
3033
  if (encoder_markers_pop(s, ident) < 0)
@@ -3124,6 +3152,96 @@ encoder_encode_dict_key(PyEncoderObject *s, PyObject *key)
3124
3152
  return encoded; /* new reference */
3125
3153
  }
3126
3154
 
3155
+ /* Write '\n' followed by indent_level copies of s->indent directly to
3156
+ * the accumulator, without materializing the combined string as an
3157
+ * intermediate PyObject. This is the newline-plus-indent prefix
3158
+ * emitted before each item of an indented array or object and before
3159
+ * the matching closing bracket.
3160
+ *
3161
+ * Writing pieces rather than building a concatenated string saves a
3162
+ * PyUnicode_FromStringAndSize + PySequence_Repeat + PyNumber_Add per
3163
+ * container (versus the previous encoder_build_indent_string helper),
3164
+ * which matters most for deeply nested structures and on 3.14+ where
3165
+ * JSON_Accu is a PyUnicodeWriter that appends in-place at near-zero
3166
+ * per-write cost. On older Python the cost is one PyList_Append per
3167
+ * piece; the extra list entries are cheap compared to the allocation
3168
+ * traffic and the PySequence_Repeat's proportional memcpy work.
3169
+ *
3170
+ * Returns 0 on success, -1 on allocation failure. Only called when
3171
+ * s->indent is not Py_None. */
3172
+ static int
3173
+ encoder_accumulate_newline_indent(PyEncoderObject *s, _speedups_state *state,
3174
+ JSON_Accu *rval, Py_ssize_t indent_level)
3175
+ {
3176
+ Py_ssize_t i;
3177
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_newline))
3178
+ return -1;
3179
+ for (i = 0; i < indent_level; i++) {
3180
+ if (JSON_Accu_Accumulate(state, rval, s->indent))
3181
+ return -1;
3182
+ }
3183
+ return 0;
3184
+ }
3185
+
3186
+ #if PY_VERSION_HEX >= 0x030B0000
3187
+ /* Attach a PEP 678 note formatted via PyUnicode_FromFormatV to the in-
3188
+ * flight exception. Mirrors the pure-Python encoder's
3189
+ * `except BaseException as exc: exc.add_note(...); raise` wrappers
3190
+ * around each recursive encode step.
3191
+ *
3192
+ * Crucially, PyErr_Fetch runs BEFORE the note is built: the `%R`
3193
+ * formatter walks into PyObject_Repr, which on debug builds
3194
+ * (cpython-3.14-debug and cpython-3.14t+debug) asserts that no
3195
+ * exception is currently set. Building the note while our caller's
3196
+ * exception is still live would core-dump those jobs, as the CI
3197
+ * failure on job 72447820967 / 72447820982 demonstrated. Fetch first,
3198
+ * format second, Restore last.
3199
+ *
3200
+ * Failures on the note path — a repr() that raises, PyUnicode_FromFormatV
3201
+ * returning NULL, a BaseException subclass whose add_note override
3202
+ * raises — are swallowed so the in-flight exception survives intact.
3203
+ *
3204
+ * Only compiled on Python 3.11+, where PEP 678 exists. */
3205
+ static void
3206
+ encoder_annotate_exception(_speedups_state *state, const char *format, ...)
3207
+ {
3208
+ PyObject *exc_type;
3209
+ PyObject *exc_value;
3210
+ PyObject *exc_tb;
3211
+ PyObject *note;
3212
+ PyObject *result;
3213
+ va_list args;
3214
+
3215
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
3216
+ if (exc_value == NULL) {
3217
+ /* No live exception to annotate — callsites always have one,
3218
+ * so this is a defensive no-op. */
3219
+ PyErr_Restore(exc_type, exc_value, exc_tb);
3220
+ return;
3221
+ }
3222
+ PyErr_NormalizeException(&exc_type, &exc_value, &exc_tb);
3223
+
3224
+ va_start(args, format);
3225
+ note = PyUnicode_FromFormatV(format, args);
3226
+ va_end(args);
3227
+
3228
+ if (note != NULL) {
3229
+ result = PyObject_CallMethodObjArgs(exc_value, state->JSON_attr_add_note,
3230
+ note, NULL);
3231
+ Py_DECREF(note);
3232
+ if (result == NULL)
3233
+ PyErr_Clear();
3234
+ else
3235
+ Py_DECREF(result);
3236
+ }
3237
+ else {
3238
+ PyErr_Clear();
3239
+ }
3240
+
3241
+ PyErr_Restore(exc_type, exc_value, exc_tb);
3242
+ }
3243
+ #endif
3244
+
3127
3245
  static int
3128
3246
  encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_ssize_t indent_level)
3129
3247
  {
@@ -3133,6 +3251,12 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3133
3251
  PyObject *encoded = NULL;
3134
3252
  PyObject *iter = NULL;
3135
3253
  PyObject *item = NULL;
3254
+ /* When s->indent is not Py_None, each inter-item boundary is emitted
3255
+ * as s->item_separator + '\n' + (s->indent * inner_indent_level) via
3256
+ * multiple JSON_Accu_Accumulate calls; no combined PyObject is
3257
+ * materialized. */
3258
+ int indented = (s->indent != Py_None);
3259
+ Py_ssize_t inner_indent_level = indented ? indent_level + 1 : indent_level;
3136
3260
  Py_ssize_t idx;
3137
3261
 
3138
3262
  {
@@ -3150,6 +3274,11 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3150
3274
  if (JSON_Accu_Accumulate(state, rval, state->JSON_open_dict))
3151
3275
  goto bail;
3152
3276
 
3277
+ if (indented) {
3278
+ if (encoder_accumulate_newline_indent(s, state, rval, inner_indent_level))
3279
+ goto bail;
3280
+ }
3281
+
3153
3282
  /* Fast path: when sort_keys is off and dct is an exact dict,
3154
3283
  * iterate with PyDict_Next to avoid allocating an items list.
3155
3284
  * Py_BEGIN_CRITICAL_SECTION prevents concurrent dict mutation
@@ -3176,8 +3305,14 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3176
3305
  Py_DECREF(value);
3177
3306
  continue;
3178
3307
  }
3179
- if (idx && JSON_Accu_Accumulate(state, rval, s->item_separator)) {
3180
- Py_DECREF(value); err = 1; break;
3308
+ if (idx) {
3309
+ if (JSON_Accu_Accumulate(state, rval, s->item_separator)) {
3310
+ Py_DECREF(value); err = 1; break;
3311
+ }
3312
+ if (indented && encoder_accumulate_newline_indent(
3313
+ s, state, rval, inner_indent_level)) {
3314
+ Py_DECREF(value); err = 1; break;
3315
+ }
3181
3316
  }
3182
3317
  if (JSON_Accu_Accumulate(state, rval, encoded)) {
3183
3318
  Py_DECREF(value); err = 1; break;
@@ -3186,7 +3321,12 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3186
3321
  if (JSON_Accu_Accumulate(state, rval, s->key_separator)) {
3187
3322
  Py_DECREF(value); err = 1; break;
3188
3323
  }
3189
- if (encoder_listencode_obj(s, rval, value, indent_level)) {
3324
+ if (encoder_listencode_obj(s, rval, value, inner_indent_level)) {
3325
+ #if PY_VERSION_HEX >= 0x030B0000
3326
+ encoder_annotate_exception(state,
3327
+ "when serializing %s item %R",
3328
+ Py_TYPE(dct)->tp_name, key);
3329
+ #endif
3190
3330
  Py_DECREF(value); err = 1; break;
3191
3331
  }
3192
3332
  Py_DECREF(value);
@@ -3223,15 +3363,26 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3223
3363
  Py_CLEAR(item);
3224
3364
  continue;
3225
3365
  }
3226
- if (idx && JSON_Accu_Accumulate(state, rval, s->item_separator))
3227
- goto bail;
3366
+ if (idx) {
3367
+ if (JSON_Accu_Accumulate(state, rval, s->item_separator))
3368
+ goto bail;
3369
+ if (indented && encoder_accumulate_newline_indent(
3370
+ s, state, rval, inner_indent_level))
3371
+ goto bail;
3372
+ }
3228
3373
  if (JSON_Accu_Accumulate(state, rval, encoded))
3229
3374
  goto bail;
3230
3375
  Py_CLEAR(encoded);
3231
3376
  if (JSON_Accu_Accumulate(state, rval, s->key_separator))
3232
3377
  goto bail;
3233
- if (encoder_listencode_obj(s, rval, value, indent_level))
3378
+ if (encoder_listencode_obj(s, rval, value, inner_indent_level)) {
3379
+ #if PY_VERSION_HEX >= 0x030B0000
3380
+ encoder_annotate_exception(state,
3381
+ "when serializing %s item %R",
3382
+ Py_TYPE(dct)->tp_name, key);
3383
+ #endif
3234
3384
  goto bail;
3385
+ }
3235
3386
  Py_CLEAR(item);
3236
3387
  idx++;
3237
3388
  }
@@ -3243,6 +3394,10 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
3243
3394
  if (encoder_markers_pop(s, ident))
3244
3395
  goto bail;
3245
3396
  ident = NULL;
3397
+ if (indented) {
3398
+ if (encoder_accumulate_newline_indent(s, state, rval, indent_level))
3399
+ goto bail;
3400
+ }
3246
3401
  if (JSON_Accu_Accumulate(state, rval, state->JSON_close_dict))
3247
3402
  goto bail;
3248
3403
  return 0;
@@ -3264,55 +3419,85 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3264
3419
  PyObject *ident = NULL;
3265
3420
  PyObject *iter = NULL;
3266
3421
  PyObject *obj = NULL;
3422
+ /* See encoder_listencode_dict: inter-item indent prefixes are
3423
+ * written piece-by-piece via encoder_accumulate_newline_indent,
3424
+ * no intermediate PyObject is materialized. */
3425
+ int indented = (s->indent != Py_None);
3426
+ Py_ssize_t inner_indent_level = indented ? indent_level + 1 : indent_level;
3267
3427
  Py_ssize_t i = 0;
3428
+ int is_exact_fast;
3268
3429
 
3269
3430
  /* Emptiness check: use direct size for exact types to skip the
3270
3431
  * __bool__/__len__ method dispatch of PyObject_IsTrue. */
3271
3432
  if (PyList_CheckExact(seq)) {
3272
3433
  if (PyList_GET_SIZE(seq) == 0)
3273
3434
  return JSON_Accu_Accumulate(state, rval, state->JSON_empty_array);
3435
+ is_exact_fast = 1;
3274
3436
  }
3275
3437
  else if (PyTuple_CheckExact(seq)) {
3276
3438
  if (PyTuple_GET_SIZE(seq) == 0)
3277
3439
  return JSON_Accu_Accumulate(state, rval, state->JSON_empty_array);
3440
+ is_exact_fast = 1;
3278
3441
  }
3279
3442
  else {
3280
- int is_true = PyObject_IsTrue(seq);
3281
- if (is_true == -1)
3282
- return -1;
3283
- if (is_true == 0)
3284
- return JSON_Accu_Accumulate(state, rval, state->JSON_empty_array);
3443
+ /* For non-exact types (list/tuple subclasses or other iterables)
3444
+ * we cannot short-circuit on emptiness without consuming the
3445
+ * iterator. iterable_as_array in particular reaches us with a
3446
+ * bare iterator whose PyObject_IsTrue always returns 1, so the
3447
+ * emptiness is only discovered on the first PyIter_Next call
3448
+ * inside the slow path below. */
3449
+ is_exact_fast = 0;
3285
3450
  }
3286
3451
 
3287
3452
  if (encoder_markers_push(s, seq, &ident))
3288
3453
  goto bail;
3289
3454
 
3290
- if (JSON_Accu_Accumulate(state, rval, state->JSON_open_array))
3291
- goto bail;
3292
-
3293
- /* Fast path: exact list or exact tuple — iterate by index to avoid
3294
- * allocating an iterator object. Py_BEGIN_CRITICAL_SECTION prevents
3295
- * concurrent list mutation on free-threaded builds (tuples are
3296
- * immutable so the lock is uncontested).
3297
- *
3298
- * Uses a local `item` variable (not the outer `obj`) so that the
3299
- * bail handler's Py_XDECREF(obj) stays a no-op for this path. */
3300
- if (PyList_CheckExact(seq) || PyTuple_CheckExact(seq)) {
3455
+ if (is_exact_fast) {
3456
+ /* Fast path: exact list or exact tuple — iterate by index to
3457
+ * avoid allocating an iterator object. Known non-empty from
3458
+ * the size check above, so the opening bracket and the first
3459
+ * newline_indent can be emitted unconditionally.
3460
+ * Py_BEGIN_CRITICAL_SECTION prevents concurrent list mutation
3461
+ * on free-threaded builds (tuples are immutable so the lock is
3462
+ * uncontested).
3463
+ *
3464
+ * Uses a local `item` variable (not the outer `obj`) so that
3465
+ * the bail handler's Py_XDECREF(obj) stays a no-op for this
3466
+ * path. */
3301
3467
  PyObject *item;
3302
3468
  Py_ssize_t size;
3303
3469
  int is_list = PyList_CheckExact(seq);
3304
3470
  int err = 0;
3305
3471
 
3472
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_open_array))
3473
+ goto bail;
3474
+
3475
+ if (indented) {
3476
+ if (encoder_accumulate_newline_indent(s, state, rval, inner_indent_level))
3477
+ goto bail;
3478
+ }
3479
+
3306
3480
  Py_BEGIN_CRITICAL_SECTION(seq);
3307
3481
  size = is_list ? PyList_GET_SIZE(seq) : PyTuple_GET_SIZE(seq);
3308
3482
  for (i = 0; i < size; i++) {
3309
3483
  item = is_list ? PyList_GET_ITEM(seq, i)
3310
3484
  : PyTuple_GET_ITEM(seq, i);
3311
3485
  Py_INCREF(item);
3312
- if (i && JSON_Accu_Accumulate(state, rval, s->item_separator)) {
3313
- Py_DECREF(item); err = 1; break;
3486
+ if (i) {
3487
+ if (JSON_Accu_Accumulate(state, rval, s->item_separator)) {
3488
+ Py_DECREF(item); err = 1; break;
3489
+ }
3490
+ if (indented && encoder_accumulate_newline_indent(
3491
+ s, state, rval, inner_indent_level)) {
3492
+ Py_DECREF(item); err = 1; break;
3493
+ }
3314
3494
  }
3315
- if (encoder_listencode_obj(s, rval, item, indent_level)) {
3495
+ if (encoder_listencode_obj(s, rval, item, inner_indent_level)) {
3496
+ #if PY_VERSION_HEX >= 0x030B0000
3497
+ encoder_annotate_exception(state,
3498
+ "when serializing %s item %zd",
3499
+ Py_TYPE(seq)->tp_name, i);
3500
+ #endif
3316
3501
  Py_DECREF(item); err = 1; break;
3317
3502
  }
3318
3503
  Py_DECREF(item);
@@ -3321,32 +3506,72 @@ encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_s
3321
3506
 
3322
3507
  if (err)
3323
3508
  goto bail;
3509
+
3510
+ if (indented) {
3511
+ if (encoder_accumulate_newline_indent(s, state, rval, indent_level))
3512
+ goto bail;
3513
+ }
3514
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_close_array))
3515
+ goto bail;
3324
3516
  }
3325
3517
  else {
3326
- /* Slow path: list/tuple subclasses or other iterables. */
3518
+ /* Slow path: list/tuple subclasses or arbitrary iterables.
3519
+ * The opening '[' and newline_indent are deferred until we
3520
+ * know there is at least one item, so an empty iterable still
3521
+ * emits a compact "[]" under an indent= setting. */
3522
+ int emitted_open = 0;
3523
+
3327
3524
  iter = PyObject_GetIter(seq);
3328
3525
  if (iter == NULL)
3329
3526
  goto bail;
3330
3527
  while ((obj = PyIter_Next(iter))) {
3331
- if (i) {
3528
+ if (!emitted_open) {
3529
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_open_array))
3530
+ goto bail;
3531
+ if (indented && encoder_accumulate_newline_indent(
3532
+ s, state, rval, inner_indent_level))
3533
+ goto bail;
3534
+ emitted_open = 1;
3535
+ }
3536
+ else {
3332
3537
  if (JSON_Accu_Accumulate(state, rval, s->item_separator))
3333
3538
  goto bail;
3539
+ if (indented && encoder_accumulate_newline_indent(
3540
+ s, state, rval, inner_indent_level))
3541
+ goto bail;
3334
3542
  }
3335
- if (encoder_listencode_obj(s, rval, obj, indent_level))
3543
+ if (encoder_listencode_obj(s, rval, obj, inner_indent_level)) {
3544
+ #if PY_VERSION_HEX >= 0x030B0000
3545
+ encoder_annotate_exception(state,
3546
+ "when serializing %s item %zd",
3547
+ Py_TYPE(seq)->tp_name, i);
3548
+ #endif
3336
3549
  goto bail;
3550
+ }
3337
3551
  i++;
3338
3552
  Py_CLEAR(obj);
3339
3553
  }
3340
3554
  Py_CLEAR(iter);
3341
3555
  if (PyErr_Occurred())
3342
3556
  goto bail;
3557
+
3558
+ if (!emitted_open) {
3559
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_empty_array))
3560
+ goto bail;
3561
+ }
3562
+ else {
3563
+ if (indented) {
3564
+ if (encoder_accumulate_newline_indent(s, state, rval, indent_level))
3565
+ goto bail;
3566
+ }
3567
+ if (JSON_Accu_Accumulate(state, rval, state->JSON_close_array))
3568
+ goto bail;
3569
+ }
3343
3570
  }
3344
3571
 
3345
3572
  if (encoder_markers_pop(s, ident))
3346
3573
  goto bail;
3347
3574
  ident = NULL;
3348
- if (JSON_Accu_Accumulate(state, rval, state->JSON_close_array))
3349
- goto bail;
3350
3575
  return 0;
3351
3576
 
3352
3577
  bail:
@@ -3513,12 +3738,14 @@ reset_speedups_state_constants(_speedups_state *state)
3513
3738
  Py_CLEAR(state->JSON_open_array);
3514
3739
  Py_CLEAR(state->JSON_close_array);
3515
3740
  Py_CLEAR(state->JSON_empty_array);
3741
+ Py_CLEAR(state->JSON_newline);
3516
3742
  Py_CLEAR(state->JSON_sortargs);
3517
3743
  Py_CLEAR(state->JSON_itemgetter0);
3518
3744
  Py_CLEAR(state->JSON_attr_for_json);
3519
3745
  Py_CLEAR(state->JSON_attr_asdict);
3520
3746
  Py_CLEAR(state->JSON_attr_sort);
3521
3747
  Py_CLEAR(state->JSON_attr_encoded_json);
3748
+ Py_CLEAR(state->JSON_attr_add_note);
3522
3749
  Py_CLEAR(state->RawJSONType);
3523
3750
  Py_CLEAR(state->JSONDecodeError);
3524
3751
  }
@@ -3584,6 +3811,9 @@ init_speedups_state(_speedups_state *state, PyObject *module)
3584
3811
  state->JSON_empty_array = JSON_InternFromString("[]");
3585
3812
  if (state->JSON_empty_array == NULL)
3586
3813
  return -1;
3814
+ state->JSON_newline = JSON_InternFromString("\n");
3815
+ if (state->JSON_newline == NULL)
3816
+ return -1;
3587
3817
  state->JSON_sortargs = PyTuple_New(0);
3588
3818
  if (state->JSON_sortargs == NULL)
3589
3819
  return -1;
@@ -3618,6 +3848,9 @@ init_speedups_state(_speedups_state *state, PyObject *module)
3618
3848
  state->JSON_attr_encoded_json = JSON_InternFromString("encoded_json");
3619
3849
  if (state->JSON_attr_encoded_json == NULL)
3620
3850
  return -1;
3851
+ state->JSON_attr_add_note = JSON_InternFromString("add_note");
3852
+ if (state->JSON_attr_add_note == NULL)
3853
+ return -1;
3621
3854
 
3622
3855
  (void)module;
3623
3856
  return 0;
@@ -372,7 +372,7 @@ class JSONEncoder(object):
372
372
  key_memo = {}
373
373
  int_as_string_bitcount = (
374
374
  53 if self.bigint_as_string else self.int_as_string_bitcount)
375
- if (c_make_encoder is not None and self.indent is None):
375
+ if c_make_encoder is not None:
376
376
  _iterencode = c_make_encoder(
377
377
  markers, self.default, _encoder, self.indent,
378
378
  self.key_separator, self.item_separator, self.sort_keys,
@@ -73,8 +73,7 @@ class TestErrors(TestCase):
73
73
  x = []
74
74
  x.append(x)
75
75
  try:
76
- # Use indent to force the Python encoder
77
- json.dumps(x, indent=2)
76
+ json.dumps(x)
78
77
  except ValueError as exc:
79
78
  self.assertEqual(
80
79
  exc.__notes__, ['when serializing list item 0'])
@@ -86,7 +85,7 @@ class TestErrors(TestCase):
86
85
  x = {}
87
86
  x['test'] = x
88
87
  try:
89
- json.dumps(x, indent=2)
88
+ json.dumps(x)
90
89
  except ValueError as exc:
91
90
  self.assertEqual(
92
91
  exc.__notes__, ["when serializing dict item 'test'"])
@@ -96,7 +95,7 @@ class TestErrors(TestCase):
96
95
  @unittest.skipIf(sys.version_info < (3, 11), 'add_note requires Python 3.11+')
97
96
  def test_add_note_nested_error(self):
98
97
  try:
99
- json.dumps({'a': [1, object(), 3]}, indent=2)
98
+ json.dumps({'a': [1, object(), 3]})
100
99
  except TypeError as exc:
101
100
  self.assertEqual(len(exc.__notes__), 3)
102
101
  self.assertEqual(exc.__notes__[0],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplejson
3
- Version: 4.0.1
3
+ Version: 4.1.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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes