From b3a2991f29b668fef807141e9d3200ca6e5d4305 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Jan 2023 21:09:32 +0000 Subject: [PATCH] =?UTF-8?q?Update=20SimpleJSON=203.16.1=20(ce75e60)=20?= =?UTF-8?q?=E2=86=92=203.18.1=20(c891b95).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 1 + lib/simplejson/__init__.py | 51 ++++++--------- lib/simplejson/_speedups.c | 124 +++++++++++++++++++++---------------- lib/simplejson/decoder.py | 2 + lib/simplejson/encoder.py | 56 +++++++++++------ 5 files changed, 130 insertions(+), 104 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4376c4c1..d8ca507b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ * Add Filelock 3.9.0 (ce3e891) * Remove Lockfile no longer used by Cachecontrol * Update Msgpack 1.0.0 (fa7d744) to 1.0.4 (b5acfd5) +* Update SimpleJSON 3.16.1 (ce75e60) to 3.18.1 (c891b95) * Update tmdbsimple 2.6.6 (679e343) to 2.9.1 (9da400a) * Update torrent_parser 0.3.0 (2a4eecb) to 0.4.0 (23b9e11) * Update unidecode module 1.1.1 (632af82) to 1.3.6 (4141992) diff --git a/lib/simplejson/__init__.py b/lib/simplejson/__init__.py index 9d9b7370..b3e8a81a 100644 --- a/lib/simplejson/__init__.py +++ b/lib/simplejson/__init__.py @@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON):: """ from __future__ import absolute_import -__version__ = '3.16.1' +__version__ = '3.18.1' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', @@ -180,18 +180,12 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, ``.write()``-supporting file-like object). If *skipkeys* is true then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + (``str``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If *ensure_ascii* is false, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. - - If *check_circular* is false, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). + If *ensure_ascii* is false (default: ``True``), then the output may + contain non-ASCII characters, so long as they do not need to be escaped + by JSON. When it is true, all non-ASCII characters are escaped. If *allow_nan* is false, then it will be a ``ValueError`` to serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) @@ -202,9 +196,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, If *indent* is a string, then JSON array elements and object members will be pretty-printed with a newline followed by that string repeated for each level of nesting. ``None`` (the default) selects the most compact - representation without any newlines. For backwards compatibility with - versions of simplejson earlier than 2.1.0, an integer is also accepted - and is converted to a string with that many spaces. + representation without any newlines. If specified, *separators* should be an ``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')`` @@ -308,13 +300,13 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, iterable_as_array=False, **kw): """Serialize ``obj`` to a JSON formatted ``str``. - If ``skipkeys`` is false then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. + If *ensure_ascii* is false (default: ``True``), then the output may + contain non-ASCII characters, so long as they do not need to be escaped + by JSON. When it is true, all non-ASCII characters are escaped. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -338,7 +330,8 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, compact JSON representation, you should specify ``(',', ':')`` to eliminate whitespace. - ``encoding`` is the character encoding for str instances, default is UTF-8. + ``encoding`` is the character encoding for bytes instances, default is + UTF-8. ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. @@ -367,7 +360,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, If specified, *item_sort_key* is a callable used to sort the items in each dictionary. This is useful if you want to sort items other than - in alphabetical order by key. This option takes precendence over + in alphabetical order by key. This option takes precedence over *sort_keys*. If *sort_keys* is true (default: ``False``), the output of dictionaries @@ -428,14 +421,11 @@ def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, use_decimal=False, namedtuple_as_object=True, tuple_as_array=True, **kw): """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing - a JSON document) to a Python object. + a JSON document as `str` or `bytes`) to a Python object. *encoding* determines the encoding used to interpret any - :class:`str` objects decoded by this instance (``'utf-8'`` by - default). It has no effect when decoding :class:`unicode` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as :class:`unicode`. + `bytes` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding `str` objects. *object_hook*, if specified, will be called with the result of every JSON object decoded and its return value will be used in place of the @@ -488,11 +478,8 @@ def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, document) to a Python object. *encoding* determines the encoding used to interpret any - :class:`str` objects decoded by this instance (``'utf-8'`` by - default). It has no effect when decoding :class:`unicode` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as :class:`unicode`. + :class:`bytes` objects decoded by this instance (``'utf-8'`` by + default). It has no effect when decoding :class:`unicode` objects. *object_hook*, if specified, will be called with the result of every JSON object decoded and its return value will be used in place of the diff --git a/lib/simplejson/_speedups.c b/lib/simplejson/_speedups.c index e7101288..ec054c72 100644 --- a/lib/simplejson/_speedups.c +++ b/lib/simplejson/_speedups.c @@ -25,6 +25,15 @@ #define JSON_InternFromString PyString_InternFromString #endif /* PY_MAJOR_VERSION < 3 */ +#if PY_VERSION_HEX < 0x03090000 +#if !defined(PyObject_CallNoArgs) +#define PyObject_CallNoArgs(callable) PyObject_CallFunctionObjArgs(callable, NULL); +#endif +#if !defined(PyObject_CallOneArg) +#define PyObject_CallOneArg(callable, arg) PyObject_CallFunctionObjArgs(callable, arg, NULL); +#endif +#endif /* PY_VERSION_HEX < 0x03090000 */ + #if PY_VERSION_HEX < 0x02070000 #if !defined(PyOS_string_to_double) #define PyOS_string_to_double json_PyOS_string_to_double @@ -108,6 +117,9 @@ JSON_Accu_Destroy(JSON_Accu *acc); #define ERR_STRING_CONTROL "Invalid control character %r at" #define ERR_STRING_ESC1 "Invalid \\X escape sequence %r" #define ERR_STRING_ESC4 "Invalid \\uXXXX escape sequence" +#define FOR_JSON_METHOD_NAME "for_json" +#define ASDICT_METHOD_NAME "_asdict" + typedef struct _PyScannerObject { PyObject_HEAD @@ -243,12 +255,10 @@ static int _convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr); static PyObject * _convertPyInt_FromSsize_t(Py_ssize_t *size_ptr); +static int +_call_json_method(PyObject *obj, const char *method_name, PyObject **result); static PyObject * encoder_encode_float(PyEncoderObject *s, PyObject *obj); -static int -_is_namedtuple(PyObject *obj); -static int -_has_for_json_hook(PyObject *obj); static PyObject * moduleinit(void); @@ -383,30 +393,26 @@ maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj) } static int -_is_namedtuple(PyObject *obj) +_call_json_method(PyObject *obj, const char *method_name, PyObject **result) { int rval = 0; - PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict"); - if (_asdict == NULL) { + PyObject *method = PyObject_GetAttrString(obj, method_name); + if (method == NULL) { PyErr_Clear(); return 0; } - rval = PyCallable_Check(_asdict); - Py_DECREF(_asdict); - return rval; -} - -static int -_has_for_json_hook(PyObject *obj) -{ - int rval = 0; - PyObject *for_json = PyObject_GetAttrString(obj, "for_json"); - if (for_json == NULL) { - PyErr_Clear(); - return 0; + if (PyCallable_Check(method)) { + PyObject *tmp = PyObject_CallNoArgs(method); + if (tmp == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + } else { + // This will set result to NULL if a TypeError occurred, + // which must be checked by the caller + *result = tmp; + rval = 1; + } } - rval = PyCallable_Check(for_json); - Py_DECREF(for_json); + Py_DECREF(method); return rval; } @@ -638,7 +644,7 @@ encoder_stringify_key(PyEncoderObject *s, PyObject *key) if (!(PyInt_CheckExact(key) || PyLong_CheckExact(key))) { /* See #118, do not trust custom str/repr */ PyObject *res; - PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, key, NULL); + PyObject *tmp = PyObject_CallOneArg((PyObject *)&PyLong_Type, key); if (tmp == NULL) { return NULL; } @@ -792,7 +798,7 @@ join_list_string(PyObject *lst) if (joinfn == NULL) return NULL; } - return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); + return PyObject_CallOneArg(joinfn, lst); } #endif /* PY_MAJOR_VERSION < 3 */ @@ -1484,7 +1490,7 @@ _parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_ /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */ if (s->pairs_hook != Py_None) { - val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL); + val = PyObject_CallOneArg(s->pairs_hook, pairs); if (val == NULL) goto bail; Py_DECREF(pairs); @@ -1494,7 +1500,7 @@ _parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_ /* if object_hook is not None: rval = object_hook(rval) */ if (s->object_hook != Py_None) { - val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + val = PyObject_CallOneArg(s->object_hook, rval); if (val == NULL) goto bail; Py_DECREF(rval); @@ -1648,7 +1654,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ss /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */ if (s->pairs_hook != Py_None) { - val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL); + val = PyObject_CallOneArg(s->pairs_hook, pairs); if (val == NULL) goto bail; Py_DECREF(pairs); @@ -1658,7 +1664,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ss /* if object_hook is not None: rval = object_hook(rval) */ if (s->object_hook != Py_None) { - val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + val = PyObject_CallOneArg(s->object_hook, rval); if (val == NULL) goto bail; Py_DECREF(rval); @@ -1851,7 +1857,7 @@ _parse_constant(PyScannerObject *s, PyObject *constant, Py_ssize_t idx, Py_ssize PyObject *rval; /* rval = parse_constant(constant) */ - rval = PyObject_CallFunctionObjArgs(s->parse_constant, constant, NULL); + rval = PyObject_CallOneArg(s->parse_constant, constant); idx += PyString_GET_SIZE(constant); *next_idx_ptr = idx; return rval; @@ -1937,7 +1943,7 @@ _match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssiz if (is_float) { /* parse as a float using a fast path if available, otherwise call user defined method */ if (s->parse_float != (PyObject *)&PyFloat_Type) { - rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + rval = PyObject_CallOneArg(s->parse_float, numstr); } else { /* rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); */ @@ -1951,7 +1957,7 @@ _match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssiz else { /* parse as an int using a fast path if available, otherwise call user defined method */ if (s->parse_int != (PyObject *)&PyInt_Type) { - rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + rval = PyObject_CallOneArg(s->parse_int, numstr); } else { rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10); @@ -2055,7 +2061,7 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ if (is_float) { /* parse as a float using a fast path if available, otherwise call user defined method */ if (s->parse_float != (PyObject *)&PyFloat_Type) { - rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + rval = PyObject_CallOneArg(s->parse_float, numstr); } else { #if PY_MAJOR_VERSION >= 3 @@ -2067,7 +2073,7 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ } else { /* no fast path for unicode -> int, just call */ - rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + rval = PyObject_CallOneArg(s->parse_int, numstr); } Py_DECREF(numstr); *next_idx_ptr = idx; @@ -2746,7 +2752,7 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj) else { /* See #118, do not trust custom str/repr */ PyObject *res; - PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyFloat_Type, obj, NULL); + PyObject *tmp = PyObject_CallOneArg((PyObject *)&PyFloat_Type, obj); if (tmp == NULL) { return NULL; } @@ -2765,7 +2771,7 @@ encoder_encode_string(PyEncoderObject *s, PyObject *obj) if (s->fast_encode) { return py_encode_basestring_ascii(NULL, obj); } - encoded = PyObject_CallFunctionObjArgs(s->encoder, obj, NULL); + encoded = PyObject_CallOneArg(s->encoder, obj); if (encoded != NULL && #if PY_MAJOR_VERSION < 3 !PyString_Check(encoded) && @@ -2796,6 +2802,7 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss /* Encode Python object obj to a JSON term, rval is a PyList */ int rv = -1; do { + PyObject *newobj; if (obj == Py_None || obj == Py_True || obj == Py_False) { PyObject *cstr = _encoded_const(obj); if (cstr != NULL) @@ -2815,7 +2822,7 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss } else { /* See #118, do not trust custom str/repr */ - PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, obj, NULL); + PyObject *tmp = PyObject_CallOneArg((PyObject *)&PyLong_Type, obj); if (tmp == NULL) { encoded = NULL; } @@ -2836,26 +2843,37 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss if (encoded != NULL) rv = _steal_accumulate(rval, encoded); } - else if (s->for_json && _has_for_json_hook(obj)) { - PyObject *newobj; - if (Py_EnterRecursiveCall(" while encoding a JSON object")) - return rv; - newobj = PyObject_CallMethod(obj, "for_json", NULL); - if (newobj != NULL) { - rv = encoder_listencode_obj(s, rval, newobj, indent_level); - Py_DECREF(newobj); + else if (s->for_json && _call_json_method(obj, FOR_JSON_METHOD_NAME, &newobj)) { + if (newobj == NULL) { + return -1; } + if (Py_EnterRecursiveCall(" while encoding a JSON object")) { + Py_DECREF(newobj); + return rv; + } + rv = encoder_listencode_obj(s, rval, newobj, indent_level); + Py_DECREF(newobj); Py_LeaveRecursiveCall(); } - else if (s->namedtuple_as_object && _is_namedtuple(obj)) { - PyObject *newobj; - if (Py_EnterRecursiveCall(" while encoding a JSON object")) - return rv; - newobj = PyObject_CallMethod(obj, "_asdict", NULL); - if (newobj != NULL) { - rv = encoder_listencode_dict(s, rval, newobj, indent_level); - Py_DECREF(newobj); + else if (s->namedtuple_as_object && _call_json_method(obj, ASDICT_METHOD_NAME, &newobj)) { + if (newobj == NULL) { + return -1; } + if (Py_EnterRecursiveCall(" while encoding a JSON object")) { + Py_DECREF(newobj); + return rv; + } + if (PyDict_Check(newobj)) { + rv = encoder_listencode_dict(s, rval, newobj, indent_level); + } else { + PyErr_Format( + PyExc_TypeError, + "_asdict() must return a dict, not %.80s", + Py_TYPE(newobj)->tp_name + ); + rv = -1; + } + Py_DECREF(newobj); Py_LeaveRecursiveCall(); } else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) { @@ -2913,7 +2931,7 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss } if (Py_EnterRecursiveCall(" while encoding a JSON object")) return rv; - newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL); + newobj = PyObject_CallOneArg(s->defaultfn, obj); if (newobj == NULL) { Py_XDECREF(ident); Py_LeaveRecursiveCall(); diff --git a/lib/simplejson/decoder.py b/lib/simplejson/decoder.py index 7f0b0568..1a8f772f 100644 --- a/lib/simplejson/decoder.py +++ b/lib/simplejson/decoder.py @@ -109,6 +109,8 @@ def py_scanstring(s, end, encoding=None, strict=True, uni = int(esc, 16) except ValueError: raise JSONDecodeError(msg, s, end - 1) + if uni < 0 or uni > _maxunicode: + raise JSONDecodeError(msg, s, end - 1) end += 5 # Check for surrogate pair on UCS-4 systems # Note that this will join high/low surrogate pairs diff --git a/lib/simplejson/encoder.py b/lib/simplejson/encoder.py index 7ea172e7..e93fe43f 100644 --- a/lib/simplejson/encoder.py +++ b/lib/simplejson/encoder.py @@ -32,6 +32,7 @@ ESCAPE_DCT = { for i in range(0x20): #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) +del i FLOAT_REPR = repr @@ -450,6 +451,15 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, not isinstance(_int_as_string_bitcount, integer_types))): raise TypeError("int_as_string_bitcount must be a positive integer") + def call_method(obj, method_name): + method = getattr(obj, method_name, None) + if callable(method): + try: + return (method(),) + except TypeError: + pass + return None + def _encode_int(value): skip_quoting = ( _int_as_string_bitcount is None @@ -512,15 +522,18 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield buf + str(value) else: yield buf - for_json = _for_json and getattr(value, 'for_json', None) - if for_json and callable(for_json): - chunks = _iterencode(for_json(), _current_indent_level) + for_json = _for_json and call_method(value, 'for_json') + if for_json: + chunks = _iterencode(for_json[0], _current_indent_level) elif isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) else: - _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) - if _asdict and callable(_asdict): - chunks = _iterencode_dict(_asdict(), + _asdict = _namedtuple_as_object and call_method(value, '_asdict') + if _asdict: + dct = _asdict[0] + if not isinstance(dct, dict): + raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) + chunks = _iterencode_dict(dct, _current_indent_level) elif _tuple_as_array and isinstance(value, tuple): chunks = _iterencode_list(value, _current_indent_level) @@ -633,15 +646,18 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif _use_decimal and isinstance(value, Decimal): yield str(value) else: - for_json = _for_json and getattr(value, 'for_json', None) - if for_json and callable(for_json): - chunks = _iterencode(for_json(), _current_indent_level) + for_json = _for_json and call_method(value, 'for_json') + if for_json: + chunks = _iterencode(for_json[0], _current_indent_level) elif isinstance(value, list): chunks = _iterencode_list(value, _current_indent_level) else: - _asdict = _namedtuple_as_object and getattr(value, '_asdict', None) - if _asdict and callable(_asdict): - chunks = _iterencode_dict(_asdict(), + _asdict = _namedtuple_as_object and call_method(value, '_asdict') + if _asdict: + dct = _asdict[0] + if not isinstance(dct, dict): + raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) + chunks = _iterencode_dict(dct, _current_indent_level) elif _tuple_as_array and isinstance(value, tuple): chunks = _iterencode_list(value, _current_indent_level) @@ -676,18 +692,20 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif isinstance(o, float): yield _floatstr(o) else: - for_json = _for_json and getattr(o, 'for_json', None) - if for_json and callable(for_json): - for chunk in _iterencode(for_json(), _current_indent_level): + for_json = _for_json and call_method(o, 'for_json') + if for_json: + for chunk in _iterencode(for_json[0], _current_indent_level): yield chunk elif isinstance(o, list): for chunk in _iterencode_list(o, _current_indent_level): yield chunk else: - _asdict = _namedtuple_as_object and getattr(o, '_asdict', None) - if _asdict and callable(_asdict): - for chunk in _iterencode_dict(_asdict(), - _current_indent_level): + _asdict = _namedtuple_as_object and call_method(o, '_asdict') + if _asdict: + dct = _asdict[0] + if not isinstance(dct, dict): + raise TypeError("_asdict() must return a dict, not %s" % (type(dct).__name__,)) + for chunk in _iterencode_dict(dct, _current_indent_level): yield chunk elif (_tuple_as_array and isinstance(o, tuple)): for chunk in _iterencode_list(o, _current_indent_level):