Skip to content

Commit

Permalink
Add indent_depth
Browse files Browse the repository at this point in the history
  • Loading branch information
nineteendo committed Nov 24, 2024
1 parent f6994b2 commit 2794531
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 42 deletions.
6 changes: 3 additions & 3 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jsonyx 2.0.0 (unreleased)
- Added the ``jsonyx`` application
- Added ``mapping_type`` and ``seq_type`` to :class:`jsonyx.Decoder`,
:func:`jsonyx.load`, :func:`jsonyx.loads` and :func:`jsonyx.read`
- Added ``commas``, ``indent_leaves``, ``mapping_types``, ``seq_types`` and
``quoted_keys`` to :class:`jsonyx.Encoder`, :func:`jsonyx.dump`,
:func:`jsonyx.dumps` and :func:`jsonyx.write`
- Added ``commas``, ``indent_depth``, ``indent_leaves``, ``mapping_types``,
``seq_types`` and ``quoted_keys`` to :class:`jsonyx.Encoder`,
:func:`jsonyx.dump`, :func:`jsonyx.dumps` and :func:`jsonyx.write`
- Added ``python -m jsonyx diff`` and ``python -m jsonyx patch``
- Added ``--indent-leaves`` and its alias ``-l`` to ``python -m jsonyx format``
- Added ``--unquoted-keys`` and its alias ``-q`` to ``python -m jsonyx format``
Expand Down
6 changes: 3 additions & 3 deletions docs/source/get-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Compact encoding
Pretty printing
^^^^^^^^^^^^^^^

.. versionchanged:: 2.0 Added ``indent_leaves``.
.. versionchanged:: 2.0 Added ``indent_depth`` and ``indent_leaves``.

>>> import jsonyx as json
>>> json.dump({"foo": [1, 2, 3], "bar": {"a": 1, "b": 2, "c": 3}}, indent=4)
Expand All @@ -139,8 +139,8 @@ Pretty printing
}

.. tip:: Use ``ensure_ascii=True`` to escape non-ASCII characters,
``indent_leaves=True`` to indent everything and ``sort_keys=True`` to sort
the keys of objects.
``indent_depth=1`` to indent up to level 1, ``indent_leaves=True`` to
indent everything and ``sort_keys=True`` to sort the keys of objects.

.. seealso:: The built-in :mod:`pprint` module for pretty-printing arbitrary
Python data structures.
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ ignore = [
"DOC201", # docstring-missing-returns
"DOC501", # docstring-missing-exception
"I001", # unsorted-imports
"INP001", # implicit-namespace-package
"PGH003", # blanket-type-ignore
"PLR0912", # too-many-branches
"PLR0913", # too-many-arguments
Expand Down
21 changes: 15 additions & 6 deletions src/jsonyx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def write(
end: str = "\n",
ensure_ascii: bool = False,
indent: int | str | None = None,
indent_depth: int | None = None,
indent_leaves: bool = False,
mapping_types: type | tuple[type, ...] = (),
quoted_keys: bool = True,
Expand All @@ -238,8 +239,8 @@ def write(
.. versionchanged:: 2.0
- Added ``commas``, ``indent_leaves``, ``mapping_types``, ``seq_types``
and ``quoted_keys``.
- Added ``commas``, ``indent_depth``, ``indent_leaves``,
``mapping_types``, ``seq_types`` and ``quoted_keys``.
- Made :class:`tuple` JSON serializable.
- Merged ``item_separator`` and ``key_separator`` as ``separators``.
Expand All @@ -250,6 +251,7 @@ def write(
:param end: the string to append at the end
:param ensure_ascii: escape non-ASCII characters
:param indent: the number of spaces or string to indent with
:param indent_depth: the depth up to which to indent
:param indent_leaves: indent leaf objects and arrays
:param mapping_types: an additional mapping type or tuple of additional
mapping types
Expand Down Expand Up @@ -286,6 +288,7 @@ def write(
end=end,
ensure_ascii=ensure_ascii,
indent=indent,
indent_depth=indent_depth,
indent_leaves=indent_leaves,
mapping_types=mapping_types,
quoted_keys=quoted_keys,
Expand All @@ -305,6 +308,7 @@ def dump(
end: str = "\n",
ensure_ascii: bool = False,
indent: int | str | None = None,
indent_depth: int | None = None,
indent_leaves: bool = False,
mapping_types: type | tuple[type, ...] = (),
quoted_keys: bool = True,
Expand All @@ -317,8 +321,8 @@ def dump(
.. versionchanged:: 2.0
- Added ``commas``, ``indent_leaves``, ``mapping_types``, ``seq_types``
and ``quoted_keys``.
- Added ``commas``, ``indent_depth``, ``indent_leaves``,
``mapping_types``, ``seq_types`` and ``quoted_keys``.
- Made :class:`tuple` JSON serializable.
- Merged ``item_separator`` and ``key_separator`` as ``separators``.
Expand All @@ -329,6 +333,7 @@ def dump(
:param end: the string to append at the end
:param ensure_ascii: escape non-ASCII characters
:param indent: the number of spaces or string to indent with
:param indent_depth: the depth up to which to indent
:param indent_leaves: indent leaf objects and arrays
:param mapping_types: an additional mapping type or tuple of additional
mapping types
Expand Down Expand Up @@ -364,6 +369,7 @@ def dump(
end=end,
ensure_ascii=ensure_ascii,
indent=indent,
indent_depth=indent_depth,
indent_leaves=indent_leaves,
mapping_types=mapping_types,
quoted_keys=quoted_keys,
Expand All @@ -382,6 +388,7 @@ def dumps(
end: str = "\n",
ensure_ascii: bool = False,
indent: int | str | None = None,
indent_depth: int | None = None,
indent_leaves: bool = False,
mapping_types: type | tuple[type, ...] = (),
quoted_keys: bool = True,
Expand All @@ -394,8 +401,8 @@ def dumps(
.. versionchanged:: 2.0
- Added ``commas``, ``indent_leaves``, ``mapping_types``, ``seq_types``
and ``quoted_keys``.
- Added ``commas``, ``indent_depth``, ``indent_leaves``,
``mapping_types``, ``seq_types`` and ``quoted_keys``.
- Made :class:`tuple` JSON serializable.
- Merged ``item_separator`` and ``key_separator`` as ``separators``.
Expand All @@ -405,6 +412,7 @@ def dumps(
:param end: the string to append at the end
:param ensure_ascii: escape non-ASCII characters
:param indent: the number of spaces or string to indent with
:param indent_depth: the depth up to which to indent
:param indent_leaves: indent leaf objects and arrays
:param mapping_types: an additional mapping type or tuple of additional
mapping types
Expand Down Expand Up @@ -436,6 +444,7 @@ def dumps(
end=end,
ensure_ascii=ensure_ascii,
indent=indent,
indent_depth=indent_depth,
indent_leaves=indent_leaves,
mapping_types=mapping_types,
quoted_keys=quoted_keys,
Expand Down
59 changes: 37 additions & 22 deletions src/jsonyx/_encoder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Copyright (C) 2024 Nice Zombies
"""JSON encoder."""
# TODO(Nice Zombies): add indent_depth=None
from __future__ import annotations

import sys
Expand Down Expand Up @@ -62,6 +61,7 @@ def make_encoder(
item_separator: str,
long_item_separator: str,
key_separator: str,
indent_depth: int,
allow_nan_and_infinity: bool, # noqa: FBT001
allow_surrogates: bool, # noqa: FBT001
ensure_ascii: bool, # noqa: FBT001
Expand Down Expand Up @@ -135,7 +135,7 @@ def decimalstr(decimal: Decimal) -> str:
return str(decimal)

def write_sequence(
seq: Any, write: _WriteFunc, old_indent: str,
seq: Any, write: _WriteFunc, indent_level: int, old_indent: str,
) -> None:
if not seq:
write("[]")
Expand All @@ -148,14 +148,16 @@ def write_sequence(
markers[markerid] = seq
write("[")
current_indent: str = old_indent
if indent is None or (not indent_leaves and all(
value is None or isinstance(value, (Decimal, float, int, str))
for value in seq
)):
if indent is None or indent_level >= indent_depth or (
not indent_leaves and all(value is None or isinstance(
value, (Decimal, float, int, str),
) for value in seq)
):
indented: bool = False
current_item_separator: str = long_item_separator
else:
indented = True
indent_level += 1
current_indent += indent
current_item_separator = item_separator + current_indent
write(current_indent)
Expand All @@ -167,7 +169,7 @@ def write_sequence(
else:
write(current_item_separator)

write_value(value, write, current_indent)
write_value(value, write, indent_level, current_indent)

del markers[markerid]
if indented:
Expand All @@ -179,7 +181,10 @@ def write_sequence(
write("]")

def write_mapping(
mapping: Any, write: _WriteFunc, old_indent: str,
mapping: Any,
write: _WriteFunc,
indent_level: int,
old_indent: str,
) -> None:
if not mapping:
write("{}")
Expand All @@ -192,14 +197,16 @@ def write_mapping(
markers[markerid] = mapping
write("{")
current_indent: str = old_indent
if indent is None or (not indent_leaves and all(
value is None or isinstance(value, (Decimal, float, int, str))
for value in mapping.values()
)):
if indent is None or indent_level >= indent_depth or (
not indent_leaves and all(value is None or isinstance(
value, (Decimal, float, int, str),
) for value in mapping.values())
):
indented: bool = False
current_item_separator: str = long_item_separator
else:
indented = True
indent_level += 1
current_indent += indent
current_item_separator = item_separator + current_indent
write(current_indent)
Expand All @@ -224,7 +231,7 @@ def write_mapping(
write(encode_string(key))

write(key_separator)
write_value(value, write, current_indent)
write_value(value, write, indent_level, current_indent)

del markers[markerid]
if indented:
Expand All @@ -236,7 +243,10 @@ def write_mapping(
write("}")

def write_value(
obj: object, write: _WriteFunc, current_indent: str,
obj: object,
write: _WriteFunc,
indent_level: int,
current_indent: str,
) -> None:
if isinstance(obj, str):
write(encode_string(obj))
Expand All @@ -252,9 +262,9 @@ def write_value(
elif isinstance(obj, float):
write(floatstr(obj))
elif isinstance(obj, (list, tuple, seq_types)):
write_sequence(obj, write, current_indent)
write_sequence(obj, write, indent_level, current_indent)
elif isinstance(obj, (dict, mapping_types)):
write_mapping(obj, write, current_indent)
write_mapping(obj, write, indent_level, current_indent)
elif isinstance(obj, Decimal):
write(decimalstr(obj))
else:
Expand All @@ -268,7 +278,7 @@ def encoder(obj: object) -> str:
io: StringIO = StringIO()
write: _WriteFunc = io.write
try:
write_value(obj, write, "\n")
write_value(obj, write, 0, "\n")
except (ValueError, TypeError) as exc:
raise exc.with_traceback(None) from None
finally:
Expand All @@ -285,8 +295,8 @@ class Encoder:
.. versionchanged:: 2.0
- Added ``commas``, ``indent_leaves``, ``mapping_types``, ``seq_types``
and ``quoted_keys``.
- Added ``commas``, ``indent_depth``, ``indent_leaves``,
``mapping_types``, ``seq_types`` and ``quoted_keys``.
- Made :class:`tuple` JSON serializable.
- Merged ``item_separator`` and ``key_separator`` as ``separators``.
Expand All @@ -295,6 +305,7 @@ class Encoder:
:param end: the string to append at the end
:param ensure_ascii: escape non-ASCII characters
:param indent: the number of spaces or string to indent with
:param indent_depth: the depth up to which to indent
:param indent_leaves: indent leaf objects and arrays
:param mapping_types: an additional mapping type or tuple of additional
mapping types
Expand All @@ -319,6 +330,7 @@ def __init__(
end: str = "\n",
ensure_ascii: bool = False,
indent: int | str | None = None,
indent_depth: int | None = None,
indent_leaves: bool = False,
mapping_types: type | tuple[type, ...] = (),
quoted_keys: bool = True,
Expand All @@ -338,11 +350,14 @@ def __init__(
if indent is not None and isinstance(indent, int):
indent *= " "

if indent_depth is None:
indent_depth = sys.maxsize

self._encoder: _EncodeFunc[object] = make_encoder(
indent, mapping_types, seq_types, end, item_separator,
long_item_separator, key_separator, "nan_and_infinity" in allow,
allow_surrogates, ensure_ascii, indent_leaves, quoted_keys,
sort_keys, commas and trailing_comma,
long_item_separator, key_separator, indent_depth,
"nan_and_infinity" in allow, allow_surrogates, ensure_ascii,
indent_leaves, quoted_keys, sort_keys, commas and trailing_comma,
)
self._errors: str = "surrogatepass" if allow_surrogates else "strict"

Expand Down
18 changes: 11 additions & 7 deletions src/jsonyx/_speedups.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ typedef struct _PyEncoderObject {
PyObject *item_separator;
PyObject *long_item_separator;
PyObject *key_separator;
Py_ssize_t indent_depth;
int allow_nan_and_infinity;
int allow_surrogates;
int ensure_ascii;
Expand Down Expand Up @@ -1281,20 +1282,22 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"indent", "mapping_types", "seq_types", "end",
"item_separator", "long_item_separator",
"key_separator", "allow_nan_and_infinity",
"allow_surrogates", "ensure_ascii",
"indent_leaves", "quoted_keys", "sort_keys",
"trailing_comma", NULL};
"key_separator", "indent_depth",
"allow_nan_and_infinity", "allow_surrogates",
"ensure_ascii", "indent_leaves", "quoted_keys",
"sort_keys", "trailing_comma", NULL};

PyEncoderObject *s;
PyObject *indent, *mapping_types, *seq_types;
PyObject *end, *item_separator, *long_item_separator, *key_separator;
Py_ssize_t indent_depth;
int allow_nan_and_infinity, allow_surrogates, ensure_ascii, indent_leaves;
int quoted_keys, sort_keys, trailing_comma;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOUUUUppppppp:make_encoder", kwlist,
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOUUUUnppppppp:make_encoder", kwlist,
&indent, &mapping_types, &seq_types,
&end, &item_separator, &long_item_separator, &key_separator,
&indent_depth,
&allow_nan_and_infinity, &allow_surrogates, &ensure_ascii,
&indent_leaves, &quoted_keys, &sort_keys, &trailing_comma))
return NULL;
Expand All @@ -1319,6 +1322,7 @@ encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
s->item_separator = item_separator;
s->long_item_separator = long_item_separator;
s->key_separator = key_separator;
s->indent_depth = indent_depth;
s->allow_nan_and_infinity = allow_nan_and_infinity;
s->allow_surrogates = allow_surrogates;
s->ensure_ascii = ensure_ascii;
Expand Down Expand Up @@ -1722,7 +1726,7 @@ encoder_listencode_mapping(PyEncoderObject *s, PyObject *markers, _PyUnicodeWrit
goto bail;

int indented;
if (s->indent == Py_None) {
if (s->indent == Py_None || indent_level >= s->indent_depth) {
indented = false;
}
else if (s->indent_leaves) {
Expand Down Expand Up @@ -1859,7 +1863,7 @@ encoder_listencode_sequence(PyEncoderObject *s, PyObject *markers, _PyUnicodeWri
goto bail;

int indented;
if (s->indent == Py_None) {
if (s->indent == Py_None || indent_level >= s->indent_depth) {
indented = false;
}
else if (s->indent_leaves) {
Expand Down
1 change: 1 addition & 0 deletions src/jsonyx/test/test_dumps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (C) 2024 Nice Zombies
"""JSON dumps tests."""
# TODO(Nice Zombies): test indent_depth
from __future__ import annotations

__all__: list[str] = []
Expand Down

0 comments on commit 2794531

Please sign in to comment.