summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorfinefoot <33361833+finefoot@users.noreply.github.com>2022-09-08 18:51:44 (GMT)
committerGitHub <noreply@github.com>2022-09-08 18:51:44 (GMT)
commitc06c001b30849d8826132c288426f35403f8a47d (patch)
tree0f0f9044e8eb0dcdbe8d931a8b9b0ddd5e204ec6 /Lib
parentaa3b4cf779b3dddb84e094879b91703354910d8c (diff)
downloadcpython-c06c001b30849d8826132c288426f35403f8a47d.zip
cpython-c06c001b30849d8826132c288426f35403f8a47d.tar.gz
cpython-c06c001b30849d8826132c288426f35403f8a47d.tar.bz2
gh-92734: Add indentation feature to reprlib.Repr (GH-92735)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/reprlib.py29
-rw-r--r--Lib/test/test_reprlib.py334
2 files changed, 359 insertions, 4 deletions
diff --git a/Lib/reprlib.py b/Lib/reprlib.py
index c33b4da..a92b3e3 100644
--- a/Lib/reprlib.py
+++ b/Lib/reprlib.py
@@ -38,7 +38,7 @@ class Repr:
def __init__(
self, *, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4,
maxset=6, maxfrozenset=6, maxdeque=6, maxstring=30, maxlong=40,
- maxother=30, fillvalue='...',
+ maxother=30, fillvalue='...', indent=None,
):
self.maxlevel = maxlevel
self.maxtuple = maxtuple
@@ -52,6 +52,7 @@ class Repr:
self.maxlong = maxlong
self.maxother = maxother
self.fillvalue = fillvalue
+ self.indent = indent
def repr(self, x):
return self.repr1(x, self.maxlevel)
@@ -66,6 +67,26 @@ class Repr:
else:
return self.repr_instance(x, level)
+ def _join(self, pieces, level):
+ if self.indent is None:
+ return ', '.join(pieces)
+ if not pieces:
+ return ''
+ indent = self.indent
+ if isinstance(indent, int):
+ if indent < 0:
+ raise ValueError(
+ f'Repr.indent cannot be negative int (was {indent!r})'
+ )
+ indent *= ' '
+ try:
+ sep = ',\n' + (self.maxlevel - level + 1) * indent
+ except TypeError as error:
+ raise TypeError(
+ f'Repr.indent must be a str, int or None, not {type(indent)}'
+ ) from error
+ return sep.join(('', *pieces, ''))[1:-len(indent) or None]
+
def _repr_iterable(self, x, level, left, right, maxiter, trail=''):
n = len(x)
if level <= 0 and n:
@@ -76,8 +97,8 @@ class Repr:
pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)]
if n > maxiter:
pieces.append(self.fillvalue)
- s = ', '.join(pieces)
- if n == 1 and trail:
+ s = self._join(pieces, level)
+ if n == 1 and trail and self.indent is None:
right = trail + right
return '%s%s%s' % (left, s, right)
@@ -124,7 +145,7 @@ class Repr:
pieces.append('%s: %s' % (keyrepr, valrepr))
if n > self.maxdict:
pieces.append(self.fillvalue)
- s = ', '.join(pieces)
+ s = self._join(pieces, level)
return '{%s}' % (s,)
def repr_str(self, x, level):
diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py
index 5119511..e7216d4 100644
--- a/Lib/test/test_reprlib.py
+++ b/Lib/test/test_reprlib.py
@@ -9,6 +9,7 @@ import shutil
import importlib
import importlib.util
import unittest
+import textwrap
from test.support import verbose
from test.support.os_helper import create_empty_file
@@ -39,6 +40,7 @@ class ReprTests(unittest.TestCase):
"maxlong": 110,
"maxother": 111,
"fillvalue": "x" * 112,
+ "indent": "x" * 113,
}
r1 = Repr()
for attr, val in example_kwargs.items():
@@ -246,6 +248,338 @@ class ReprTests(unittest.TestCase):
r(y)
r(z)
+ def test_valid_indent(self):
+ test_cases = [
+ {
+ 'object': (),
+ 'tests': (
+ (dict(indent=None), '()'),
+ (dict(indent=False), '()'),
+ (dict(indent=True), '()'),
+ (dict(indent=0), '()'),
+ (dict(indent=1), '()'),
+ (dict(indent=4), '()'),
+ (dict(indent=4, maxlevel=2), '()'),
+ (dict(indent=''), '()'),
+ (dict(indent='-->'), '()'),
+ (dict(indent='....'), '()'),
+ ),
+ },
+ {
+ 'object': '',
+ 'tests': (
+ (dict(indent=None), "''"),
+ (dict(indent=False), "''"),
+ (dict(indent=True), "''"),
+ (dict(indent=0), "''"),
+ (dict(indent=1), "''"),
+ (dict(indent=4), "''"),
+ (dict(indent=4, maxlevel=2), "''"),
+ (dict(indent=''), "''"),
+ (dict(indent='-->'), "''"),
+ (dict(indent='....'), "''"),
+ ),
+ },
+ {
+ 'object': [1, 'spam', {'eggs': True, 'ham': []}],
+ 'tests': (
+ (dict(indent=None), '''\
+ [1, 'spam', {'eggs': True, 'ham': []}]'''),
+ (dict(indent=False), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=True), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=0), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=1), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=4), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=4, maxlevel=2), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent=''), '''\
+ [
+ 1,
+ 'spam',
+ {
+ 'eggs': True,
+ 'ham': [],
+ },
+ ]'''),
+ (dict(indent='-->'), '''\
+ [
+ -->1,
+ -->'spam',
+ -->{
+ -->-->'eggs': True,
+ -->-->'ham': [],
+ -->},
+ ]'''),
+ (dict(indent='....'), '''\
+ [
+ ....1,
+ ....'spam',
+ ....{
+ ........'eggs': True,
+ ........'ham': [],
+ ....},
+ ]'''),
+ ),
+ },
+ {
+ 'object': {
+ 1: 'two',
+ b'three': [
+ (4.5, 6.7),
+ [set((8, 9)), frozenset((10, 11))],
+ ],
+ },
+ 'tests': (
+ (dict(indent=None), '''\
+ {1: 'two', b'three': [(4.5, 6.7), [{8, 9}, frozenset({10, 11})]]}'''),
+ (dict(indent=False), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent=True), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent=0), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent=1), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent=4), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent=4, maxlevel=2), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (...),
+ [...],
+ ],
+ }'''),
+ (dict(indent=''), '''\
+ {
+ 1: 'two',
+ b'three': [
+ (
+ 4.5,
+ 6.7,
+ ),
+ [
+ {
+ 8,
+ 9,
+ },
+ frozenset({
+ 10,
+ 11,
+ }),
+ ],
+ ],
+ }'''),
+ (dict(indent='-->'), '''\
+ {
+ -->1: 'two',
+ -->b'three': [
+ -->-->(
+ -->-->-->4.5,
+ -->-->-->6.7,
+ -->-->),
+ -->-->[
+ -->-->-->{
+ -->-->-->-->8,
+ -->-->-->-->9,
+ -->-->-->},
+ -->-->-->frozenset({
+ -->-->-->-->10,
+ -->-->-->-->11,
+ -->-->-->}),
+ -->-->],
+ -->],
+ }'''),
+ (dict(indent='....'), '''\
+ {
+ ....1: 'two',
+ ....b'three': [
+ ........(
+ ............4.5,
+ ............6.7,
+ ........),
+ ........[
+ ............{
+ ................8,
+ ................9,
+ ............},
+ ............frozenset({
+ ................10,
+ ................11,
+ ............}),
+ ........],
+ ....],
+ }'''),
+ ),
+ },
+ ]
+ for test_case in test_cases:
+ with self.subTest(test_object=test_case['object']):
+ for repr_settings, expected_repr in test_case['tests']:
+ with self.subTest(repr_settings=repr_settings):
+ r = Repr()
+ for attribute, value in repr_settings.items():
+ setattr(r, attribute, value)
+ resulting_repr = r.repr(test_case['object'])
+ expected_repr = textwrap.dedent(expected_repr)
+ self.assertEqual(resulting_repr, expected_repr)
+
+ def test_invalid_indent(self):
+ test_object = [1, 'spam', {'eggs': True, 'ham': []}]
+ test_cases = [
+ (-1, (ValueError, '[Nn]egative|[Pp]ositive')),
+ (-4, (ValueError, '[Nn]egative|[Pp]ositive')),
+ ((), (TypeError, None)),
+ ([], (TypeError, None)),
+ ((4,), (TypeError, None)),
+ ([4,], (TypeError, None)),
+ (object(), (TypeError, None)),
+ ]
+ for indent, (expected_error, expected_msg) in test_cases:
+ with self.subTest(indent=indent):
+ r = Repr()
+ r.indent = indent
+ expected_msg = expected_msg or f'{type(indent)}'
+ with self.assertRaisesRegex(expected_error, expected_msg):
+ r.repr(test_object)
+
def write_file(path, text):
with open(path, 'w', encoding='ASCII') as fp:
fp.write(text)