From 237726b0ebacce4cae4714468e628514b01aaaf8 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Thu, 12 May 2022 17:20:07 +0200 Subject: [PATCH 01/17] Add reprlib.Repr.indent Add an indentation feature to the reprlib.Repr class. This includes the actual implementation as well as added tests and an attribute description for the docs. --- Doc/library/reprlib.rst | 10 ++++ Lib/reprlib.py | 18 +++++- Lib/test/test_reprlib.py | 56 +++++++++++++++++++ ...2-05-12-15-19-00.gh-issue-92734.d0wjDt.rst | 1 + 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 4b37c5ba60f4e6..846005b79b5f61 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -123,6 +123,16 @@ which format specific object types. similar manner as :attr:`maxstring`. The default is ``20``. +.. attribute:: Repr.indent + + If this attribute is set to ``None`` (default), no indentation of the output + takes place. If it is set to either a string or an integer value, each + recursive representation will be on a new line, indented according to the + recursion depth. In that case, the attribute defines the string used for each + level of indentation. An integer value is equivalent to a string of space + characters of the same length. + + .. method:: Repr.repr(obj) The equivalent to the built-in :func:`repr` that uses the formatting imposed by diff --git a/Lib/reprlib.py b/Lib/reprlib.py index f3518df105e418..f29a0fea320394 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -48,6 +48,7 @@ def __init__(self): self.maxstring = 30 self.maxlong = 40 self.maxother = 30 + self.indent = None def repr(self, x): return self.repr1(x, self.maxlevel) @@ -62,6 +63,17 @@ def repr1(self, x, level): 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): + indent *= ' ' + sep = ',\n' + (self.maxlevel - level + 1) * indent + return sep.join(('', *pieces, ''))[1:-len(indent)] + def _repr_iterable(self, x, level, left, right, maxiter, trail=''): n = len(x) if level <= 0 and n: @@ -72,8 +84,8 @@ def _repr_iterable(self, x, level, left, right, maxiter, trail=''): 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) @@ -120,7 +132,7 @@ def repr_dict(self, x, level): 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 aa326399ab2247..86c1459a621138 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -224,6 +224,62 @@ def test_unsortable(self): r(y) r(z) + def test_indent(self): + # Initialize two Repr instances: r1 without indentation, r2 with + # 4 space characters of indentation + r1 = Repr() + r2 = Repr() + r2.indent = 4 + # Explicit example + x = [[[1]]] + self.assertEqual( + r2.repr(x), + "\n".join( + ( + "[", + " [", + " [", + " 1,", + " ],", + " ],", + "]", + ) + ) + ) + # Explicit example using a custom indentation string + r2.indent = "...." + self.assertEqual( + r2.repr(x), + "\n".join( + ( + "[", + "....[", + "........[", + "............1,", + "........],", + "....],", + "]", + ) + ) + ) + # Reset r2 to use space characters + r2.indent = 4 + # Results are the same after removing whitespace and commas + y = [1, [2, "foo", b"bar", {"a": 1, "b": "abc def ghi", "c": {1: 2, 3: 4, 5: [], 6: {}}}], 3] + self.assertNotEqual(r1.repr(y), r2.repr(y)) + self.assertEqual( + "".join(r1.repr(y).replace(",", "").split()), + "".join(r2.repr(y).replace(",", "").split()) + ) + # Same as above but with reduced levels + r1.maxlevel = 2 + r2.maxlevel = 2 + self.assertNotEqual(r1.repr(y), r2.repr(y)) + self.assertEqual( + "".join(r1.repr(y).replace(",", "").split()), + "".join(r2.repr(y).replace(",", "").split()) + ) + def write_file(path, text): with open(path, 'w', encoding='ASCII') as fp: fp.write(text) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst new file mode 100644 index 00000000000000..3dddc7a9c3c88c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst @@ -0,0 +1 @@ +Add an indentation feature to ``reprlib.Repr`` From f8a2db1494d06d7ba534fdc5c989caed1964c22d Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sat, 14 May 2022 20:07:03 +0200 Subject: [PATCH 02/17] Allow zero-length indentation string --- Lib/reprlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index f29a0fea320394..767506cd335fa2 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -72,7 +72,7 @@ def _join(self, pieces, level): if isinstance(indent, int): indent *= ' ' sep = ',\n' + (self.maxlevel - level + 1) * indent - return sep.join(('', *pieces, ''))[1:-len(indent)] + return sep.join(('', *pieces, ''))[1:-len(indent) or None] def _repr_iterable(self, x, level, left, right, maxiter, trail=''): n = len(x) From 721357c7250063b2d3bba4cd73e623b935af4ee2 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sat, 14 May 2022 21:11:42 +0200 Subject: [PATCH 03/17] Add error handling for Repr.indent --- Lib/reprlib.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 767506cd335fa2..4f398bd4dbda28 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -69,6 +69,12 @@ def _join(self, pieces, level): if not pieces: return '' indent = self.indent + if not isinstance(indent, str): + if not isinstance(indent, int) or indent < 0: + raise ValueError( + "Repr.indent must be None, str or non-negative int, not " + + repr(indent) + ) if isinstance(indent, int): indent *= ' ' sep = ',\n' + (self.maxlevel - level + 1) * indent From 7cd769bcb2d59a6701fbdb62f160bf4bd92ea36e Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sat, 14 May 2022 21:38:03 +0200 Subject: [PATCH 04/17] Improve docs for Repr.indent Co-authored-by: CAM Gerlach Co-authored-by: CAM Gerlach --- Doc/library/reprlib.rst | 58 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 846005b79b5f61..d3f0ed7e22af5d 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -125,12 +125,58 @@ which format specific object types. .. attribute:: Repr.indent - If this attribute is set to ``None`` (default), no indentation of the output - takes place. If it is set to either a string or an integer value, each - recursive representation will be on a new line, indented according to the - recursion depth. In that case, the attribute defines the string used for each - level of indentation. An integer value is equivalent to a string of space - characters of the same length. + If this attribute is set to ``None`` (the default), the output is formatted + with no line breaks or indentation, like the standard :func:`repr`. + For example: + + .. code-block:: pycon + + >>> example = [ + 1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] + >>> print(reprlib.repr(example)) + [1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] + + If :attr:`~Repr.indent` is set to a string, each recursion level + is placed on its own line, indented by that string: + + .. code-block:: pycon + + >>> reprlib.aRepr.indent = '-->' + >>> print(reprlib.repr(example)) + [ + -->1, + -->'spam', + -->{ + -->-->'a': 2, + -->-->'b': 'spam eggs', + -->-->'c': { + -->-->-->3: 4.5, + -->-->-->6: [], + -->-->}, + -->}, + -->'ham', + ] + + Setting :attr:`~Repr.indent` to a positive integer value behaves as if it + was set to a string with that number of spaces: + + .. code-block:: pycon + + >>> reprlib.aRepr.indent = 4 + >>> print(reprlib.repr(example)) + [ + 1, + 'spam', + { + 'a': 2, + 'b': 'spam eggs', + 'c': { + 3: 4.5, + 6: [], + }, + }, + 'ham', + ] .. method:: Repr.repr(obj) From 55381f6593df68a5e1a1267bed72e4a6973613bf Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sun, 15 May 2022 12:29:46 +0200 Subject: [PATCH 05/17] Differentiate between TypeError and ValueError Co-authored-by: CAM Gerlach --- Lib/reprlib.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 4f398bd4dbda28..36fe90c0aecccc 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -69,15 +69,17 @@ def _join(self, pieces, level): if not pieces: return '' indent = self.indent - if not isinstance(indent, str): - if not isinstance(indent, int) or indent < 0: - raise ValueError( - "Repr.indent must be None, str or non-negative int, not " - + repr(indent) - ) if isinstance(indent, int): + if indent < 0: + raise ValueError( + 'Repr.indent cannot be negative int (was {indent!r})') indent *= ' ' - sep = ',\n' + (self.maxlevel - level + 1) * indent + try: + sep = ',\n' + (self.maxlevel - level + 1) * indent + except TypeError as error: + raise TypeError( + f'Repr.indent must be of type None, str or int, not {type(indent)}' + ) from error return sep.join(('', *pieces, ''))[1:-len(indent) or None] def _repr_iterable(self, x, level, left, right, maxiter, trail=''): From b8e5c3b0ae1fdcb41c8643652d6990e59d3fd2ec Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sun, 15 May 2022 12:31:14 +0200 Subject: [PATCH 06/17] Fix missing f-string indicator --- Lib/reprlib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 36fe90c0aecccc..639182f2956a1e 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -72,7 +72,8 @@ def _join(self, pieces, level): if isinstance(indent, int): if indent < 0: raise ValueError( - 'Repr.indent cannot be negative int (was {indent!r})') + f'Repr.indent cannot be negative int (was {indent!r})' + ) indent *= ' ' try: sep = ',\n' + (self.maxlevel - level + 1) * indent From 21297224bedfe24869164b46aa9a3f3bf517bd70 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Sun, 15 May 2022 12:34:41 +0200 Subject: [PATCH 07/17] Fix NoneType typo and max line length --- Lib/reprlib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 639182f2956a1e..9d8e83ecbfb46a 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -79,7 +79,8 @@ def _join(self, pieces, level): sep = ',\n' + (self.maxlevel - level + 1) * indent except TypeError as error: raise TypeError( - f'Repr.indent must be of type None, str or int, not {type(indent)}' + 'Repr.indent must be of type NoneType, str or int, ' + f'not {type(indent)}' ) from error return sep.join(('', *pieces, ''))[1:-len(indent) or None] From 1d652a5be697c74a014644cff1345cf434450e5b Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Mon, 16 May 2022 00:15:44 +0200 Subject: [PATCH 08/17] Add more tests and split them into valid and invalid --- Lib/test/test_reprlib.py | 325 ++++++++++++++++++++++++++++++++------- 1 file changed, 270 insertions(+), 55 deletions(-) diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 86c1459a621138..ba70d56387754d 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -9,6 +9,7 @@ import importlib import importlib.util import unittest +import textwrap from test.support import verbose from test.support.os_helper import create_empty_file @@ -224,61 +225,275 @@ def test_unsortable(self): r(y) r(z) - def test_indent(self): - # Initialize two Repr instances: r1 without indentation, r2 with - # 4 space characters of indentation - r1 = Repr() - r2 = Repr() - r2.indent = 4 - # Explicit example - x = [[[1]]] - self.assertEqual( - r2.repr(x), - "\n".join( - ( - "[", - " [", - " [", - " 1,", - " ],", - " ],", - "]", - ) - ) - ) - # Explicit example using a custom indentation string - r2.indent = "...." - self.assertEqual( - r2.repr(x), - "\n".join( - ( - "[", - "....[", - "........[", - "............1,", - "........],", - "....],", - "]", - ) - ) - ) - # Reset r2 to use space characters - r2.indent = 4 - # Results are the same after removing whitespace and commas - y = [1, [2, "foo", b"bar", {"a": 1, "b": "abc def ghi", "c": {1: 2, 3: 4, 5: [], 6: {}}}], 3] - self.assertNotEqual(r1.repr(y), r2.repr(y)) - self.assertEqual( - "".join(r1.repr(y).replace(",", "").split()), - "".join(r2.repr(y).replace(",", "").split()) - ) - # Same as above but with reduced levels - r1.maxlevel = 2 - r2.maxlevel = 2 - self.assertNotEqual(r1.repr(y), r2.repr(y)) - self.assertEqual( - "".join(r1.repr(y).replace(",", "").split()), - "".join(r2.repr(y).replace(",", "").split()) - ) + def test_valid_indent(self): + test_cases = [ + { + 'object': (), + 'tests': ( + (dict(indent=None), '()'), + (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=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=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=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: + for repr_settings, expected_repr in test_case['tests']: + with self.subTest( + test_object=test_case['object'], repr_settings=repr_settings + ): + r = Repr() + for attribute, value in repr_settings.items(): + setattr(r, attribute, value) + expected_repr = textwrap.dedent(expected_repr) + self.assertEqual(r.repr(test_case['object']), 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: From 5f39d2fe2afcb549952d2827c78014de5fb8c779 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Mon, 16 May 2022 00:17:59 +0200 Subject: [PATCH 09/17] Make TypeError message idiomatic Co-authored-by: CAM Gerlach --- Lib/reprlib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/reprlib.py b/Lib/reprlib.py index 9d8e83ecbfb46a..5017a14269bea1 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -79,8 +79,7 @@ def _join(self, pieces, level): sep = ',\n' + (self.maxlevel - level + 1) * indent except TypeError as error: raise TypeError( - 'Repr.indent must be of type NoneType, str or int, ' - f'not {type(indent)}' + f'Repr.indent must be a str, int or None, not {type(indent)}' ) from error return sep.join(('', *pieces, ''))[1:-len(indent) or None] From 7cc9fc839a41031d322aea989d7595f2fee6851e Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Mon, 16 May 2022 12:43:40 +0200 Subject: [PATCH 10/17] Add tests for bool values --- Lib/test/test_reprlib.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index ba70d56387754d..6f7eb43ccbc148 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -231,6 +231,8 @@ def test_valid_indent(self): 'object': (), 'tests': ( (dict(indent=None), '()'), + (dict(indent=False), '()'), + (dict(indent=True), '()'), (dict(indent=0), '()'), (dict(indent=1), '()'), (dict(indent=4), '()'), @@ -244,6 +246,8 @@ def test_valid_indent(self): 'object': '', 'tests': ( (dict(indent=None), "''"), + (dict(indent=False), "''"), + (dict(indent=True), "''"), (dict(indent=0), "''"), (dict(indent=1), "''"), (dict(indent=4), "''"), @@ -258,6 +262,24 @@ def test_valid_indent(self): '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, @@ -334,6 +356,46 @@ def test_valid_indent(self): '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', From 61a6b5aed6fd6d1edaa70ee64451ce8d02ad98cf Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Mon, 16 May 2022 12:46:02 +0200 Subject: [PATCH 11/17] Use nested calls to TestCase.subTest --- Lib/test/test_reprlib.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 6f7eb43ccbc148..a14eba7042c39e 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -528,15 +528,15 @@ def test_valid_indent(self): }, ] for test_case in test_cases: - for repr_settings, expected_repr in test_case['tests']: - with self.subTest( - test_object=test_case['object'], repr_settings=repr_settings - ): - r = Repr() - for attribute, value in repr_settings.items(): - setattr(r, attribute, value) - expected_repr = textwrap.dedent(expected_repr) - self.assertEqual(r.repr(test_case['object']), expected_repr) + 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': []}] From d228f8f2935aa3280fc8faa9573c96de94e338b7 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:30:34 +0200 Subject: [PATCH 12/17] Update Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Langa --- .../2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst index 3dddc7a9c3c88c..a2fcd1ed3dc76f 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst @@ -1 +1 @@ -Add an indentation feature to ``reprlib.Repr`` +Allow multi-element reprs emitted by :mod:`reprlib` to be pretty-printed using configurable indentation. From f7e91b6cb5deec8bf637707b000c4f30e890c5bb Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Tue, 28 Jun 2022 15:41:28 +0200 Subject: [PATCH 13/17] Rename Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst to Misc/NEWS.d/next/Library/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst --- .../2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins => Library}/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst b/Misc/NEWS.d/next/Library/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst rename to Misc/NEWS.d/next/Library/2022-05-12-15-19-00.gh-issue-92734.d0wjDt.rst From df43a670847aff53a25c94639f873c28d59cc702 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:01:49 +0200 Subject: [PATCH 14/17] Use fresh reprlib.Repr instance in docs examples instead of global reprlib.aRepr --- Doc/library/reprlib.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index d3f0ed7e22af5d..3d403675853aa7 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -133,7 +133,9 @@ which format specific object types. >>> example = [ 1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] - >>> print(reprlib.repr(example)) + >>> import reprlib + >>> aRepr = reprlib.Repr() + >>> print(aRepr.repr(example)) [1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] If :attr:`~Repr.indent` is set to a string, each recursion level @@ -141,8 +143,8 @@ which format specific object types. .. code-block:: pycon - >>> reprlib.aRepr.indent = '-->' - >>> print(reprlib.repr(example)) + >>> aRepr.indent = '-->' + >>> print(aRepr.repr(example)) [ -->1, -->'spam', @@ -162,8 +164,8 @@ which format specific object types. .. code-block:: pycon - >>> reprlib.aRepr.indent = 4 - >>> print(reprlib.repr(example)) + >>> aRepr.indent = 4 + >>> print(aRepr.repr(example)) [ 1, 'spam', From a32032f2dd1448c5fbf9f727972d1d58e9a54a46 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:48:50 +0200 Subject: [PATCH 15/17] Add versionadded to docs Co-authored-by: CAM Gerlach --- Doc/library/reprlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 3d403675853aa7..62c50cae6765fa 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -179,7 +179,7 @@ which format specific object types. }, 'ham', ] - + .. versionadded:: 3.12 .. method:: Repr.repr(obj) From 8c26437b0199f89770ac8415f9707455b19f9933 Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:51:39 +0200 Subject: [PATCH 16/17] Add line breaks --- Doc/library/reprlib.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 62c50cae6765fa..24a973a24dc4e0 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -179,8 +179,10 @@ which format specific object types. }, 'ham', ] + .. versionadded:: 3.12 + .. method:: Repr.repr(obj) The equivalent to the built-in :func:`repr` that uses the formatting imposed by From f8a5450301a919f3d780eb2b4250689a3b92312d Mon Sep 17 00:00:00 2001 From: finefoot <33361833+finefoot@users.noreply.github.com> Date: Fri, 8 Jul 2022 00:49:10 +0200 Subject: [PATCH 17/17] Adjust to recent changes from #94343 --- Doc/library/reprlib.rst | 2 +- Lib/test/test_reprlib.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 4233a89d35bc25..5ebb0a7780c37b 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -19,7 +19,7 @@ This module provides a class, an instance, and a function: .. class:: Repr(*, 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) Class which provides formatting services useful in implementing functions similar to the built-in :func:`repr`; size limits for different object types diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index b3c6b14f7da68c..e7216d427200c1 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -40,6 +40,7 @@ def test_init_kwargs(self): "maxlong": 110, "maxother": 111, "fillvalue": "x" * 112, + "indent": "x" * 113, } r1 = Repr() for attr, val in example_kwargs.items():