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.
- {simplejson-4.0.1 → simplejson-4.1.0}/CHANGES.txt +19 -0
- {simplejson-4.0.1/simplejson.egg-info → simplejson-4.1.0}/PKG-INFO +1 -1
- {simplejson-4.0.1 → simplejson-4.1.0}/conf.py +2 -2
- {simplejson-4.0.1 → simplejson-4.1.0}/setup.py +1 -1
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/__init__.py +1 -1
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/_speedups.c +265 -32
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/encoder.py +1 -1
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_errors.py +3 -4
- {simplejson-4.0.1 → simplejson-4.1.0/simplejson.egg-info}/PKG-INFO +1 -1
- {simplejson-4.0.1 → simplejson-4.1.0}/LICENSE.txt +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/MANIFEST.in +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/README.rst +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/index.rst +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/pyproject.toml +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/scripts/make_docs.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/setup.cfg +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/_speedups_scan.h +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/compat.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/decoder.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/errors.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/ordered_dict.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/raw_json.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/scanner.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/__init__.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/_cibw_runner.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/_helpers.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_bigint_as_string.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_bitsize_int_as_string.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_check_circular.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_decimal.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_decode.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_default.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_dump.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_encode_basestring_ascii.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_encode_for_html.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_fail.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_float.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_for_json.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_free_threading.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_indent.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_item_sort_key.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_iterable.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_namedtuple.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass1.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass2.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_pass3.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_raw_json.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_recursion.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_scanstring.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_separators.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_speedups.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_str_subclass.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_subclass.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_subinterpreters.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_tool.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_tuple.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tests/test_unicode.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson/tool.py +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/SOURCES.txt +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/dependency_links.txt +0 -0
- {simplejson-4.0.1 → simplejson-4.1.0}/simplejson.egg-info/top_level.txt +0 -0
- {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
|
|
@@ -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.
|
|
45
|
+
version = '4.1'
|
|
46
46
|
# The full version, including alpha/beta/rc tags.
|
|
47
|
-
release = '4.0
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
3180
|
-
|
|
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,
|
|
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
|
|
3227
|
-
|
|
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,
|
|
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
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
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 (
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
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
|
|
3313
|
-
|
|
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,
|
|
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
|
|
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 (
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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]}
|
|
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],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|