From 71250ecd093414d02952b1e9809c53a7b0b224c8 Mon Sep 17 00:00:00 2001 From: Michael Bentley Date: Thu, 16 Sep 2021 01:35:50 -0600 Subject: [PATCH 1/7] pprint: Add simple and partial implementation for depth of 1. This change is to provide a simple implementation of pprint to replace the dummy placeholder implementation. This implementation should not be too slow nor take too much space on disk nor memory (which is the point of MicroPython). This does not implement the full specification from CPython, but enough for simple uses. It simply expands lists, sets, and dictionaries to multiple lines if there are more than one element. Useful for simple printing of lists, dictionaries, and sets to the console, especially when using the REPL. --- python-stdlib/pprint/pprint.py | 61 +++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index 3c140cadc..9de4aa809 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -1,6 +1,59 @@ -def pformat(obj): - return repr(obj) +from io import StringIO as _StringIO +from sys import stdout as _stdout -def pprint(obj): - print(repr(obj)) +def pformat(obj, indent=1, sort_dicts=True): + """Simply calls pprint() but capturing prints into a string and returning""" + stream = _StringIO() + pprint(obj, stream=stream, indent=indent, sort_dicts=sort_dicts) + return stream.getvalue() + + +def pp(obj, *args, sort_dicts=False, **kwargs): + """Same as pprint() except the default value of sort_dicts is False""" + pprint(obj, *args, sort_dicts=sort_dicts, **kwargs) + + +def pprint(obj, stream=None, indent=1, sort_dicts=True): + """Simple implementation of a pretty-printer. + + For simplicity, this does not recurse down. It does not produce the same + output as the CPython implementation. Instead, it is intended to simply + expand lists and dictionaries to multiple lines for easy viewing. + """ + if not stream: + stream = _stdout + islist = isinstance(obj, list) + isset = isinstance(obj, set) + isdict = isinstance(obj, dict) + if (islist or isset or isdict) and len(obj) > 1: + print("[" if islist else "{", end="", file=stream) + print(" " * (indent - 1), end="", file=stream) + items = obj + if isdict: + items = obj.items() + if sort_dicts: + items = sorted(items, key=lambda x: x[0]) + first = True + for i in items: + if not first: + print(",\n" + " " * indent, end="", file=stream) + first = False + print(f"{repr(i[0])}: {repr(i[1])}" if isdict else repr(i), end="", file=stream) + print("]" if islist else "}", file=stream) + else: + # for non-builtin types or builtin types with only one element, just + # print + print(repr(obj), file=stream) + + +def ppdir(obj, *args, hidden=False, **kwargs): + """Like calling pprint(dir(obj)) + + This method is for listing the available attributes and methods of an + object. The main difference is that you can hide elements that start with + an underscore by specifying hidden=False, which is the default. + + Additional arguments are forwarded on to pprint(). + """ + pprint([x for x in dir(obj) if not x.startswith("_")], *args, **kwargs) From 0849b72c5c0f1dd4ab0aad916f75a56792f1bbaf Mon Sep 17 00:00:00 2001 From: Michael Bentley Date: Thu, 16 Sep 2021 05:01:48 -0600 Subject: [PATCH 2/7] pprint: Refactor implementation and add PrettyPrint class. --- python-stdlib/pprint/pprint.py | 143 +++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 24 deletions(-) diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index 9de4aa809..8ecff3806 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -1,11 +1,36 @@ from io import StringIO as _StringIO from sys import stdout as _stdout +from machine import const as _const + + +class PrettyPrinter: + """A convenience class for storing pretty printer settings. + + Wraps around pformat() and pprint() calls, saving the settings for each + call. For parameter descriptions, look at pprint() documentation. + """ + + def __init__(self, indent=1, stream=None, sort_dicts=True): + self.indent = indent + self.stream = stream if stream else _stdout + self.sort_dicts = sort_dicts + + def pformat(self, obj): + """Call pformat() function with cached constructor values""" + return pformat(obj, indent=self.indent, sort_dicts=self.sort_dicts) + + def pprint(self, obj): + """Call pprint() function with cached constructor values""" + pprint(obj, stream=self.stream, indent=self.indent, sort_dicts=self.sort_dicts) def pformat(obj, indent=1, sort_dicts=True): - """Simply calls pprint() but capturing prints into a string and returning""" + """Simply calls pprint() but capturing prints into a string and returning. + + Does not add a newline to the end like pprint() does. + """ stream = _StringIO() - pprint(obj, stream=stream, indent=indent, sort_dicts=sort_dicts) + _pprint_impl(obj, stream=stream, indent=indent, sort_dicts=sort_dicts) return stream.getvalue() @@ -20,31 +45,29 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True): For simplicity, this does not recurse down. It does not produce the same output as the CPython implementation. Instead, it is intended to simply expand lists and dictionaries to multiple lines for easy viewing. + + Only expands containers. Currently supported containers are set, list, + dict, and tuple. + + stream + The desired output stream. If omitted (or False), the standard output + stream will be used. + + indent + Number of space to indent for each level of nesting. + + sort_dicts + If true, dict keys are sorted. You must ensure that the dicts + encountered can be sorted against each other (for example, int cannot + compare against a tuple, both of which can be dict keys). + + Note: raises TypeError if sort_dicts is on and sorting finds a type + mismatch """ if not stream: stream = _stdout - islist = isinstance(obj, list) - isset = isinstance(obj, set) - isdict = isinstance(obj, dict) - if (islist or isset or isdict) and len(obj) > 1: - print("[" if islist else "{", end="", file=stream) - print(" " * (indent - 1), end="", file=stream) - items = obj - if isdict: - items = obj.items() - if sort_dicts: - items = sorted(items, key=lambda x: x[0]) - first = True - for i in items: - if not first: - print(",\n" + " " * indent, end="", file=stream) - first = False - print(f"{repr(i[0])}: {repr(i[1])}" if isdict else repr(i), end="", file=stream) - print("]" if islist else "}", file=stream) - else: - # for non-builtin types or builtin types with only one element, just - # print - print(repr(obj), file=stream) + _pprint_impl(obj, stream, indent, sort_dicts) + stream.write("\n") # end in a newline def ppdir(obj, *args, hidden=False, **kwargs): @@ -57,3 +80,75 @@ def ppdir(obj, *args, hidden=False, **kwargs): Additional arguments are forwarded on to pprint(). """ pprint([x for x in dir(obj) if not x.startswith("_")], *args, **kwargs) + + +# Private implementation + + +def _pprint_impl(obj, stream, indent, sort_dicts): + type_code = _container_type(obj) + if type_code != _NONCONTAINER and len(obj) > 1: + if type_code & _SIMPLE: + _pprint_simple_container(obj, type_code, stream, indent, sort_dicts) + else: # type_code & _DICT: + _pprint_dict(obj, type_code, stream, indent, sort_dicts) + else: + # directly print noncontainers or containers with one or fewer elements + stream.write(repr(obj)) + + +_NONCONTAINER = _const(0) +_SIMPLE = _const(1) +_LIST = _const(2) +_SET = _const(4) +_TUPLE = _const(8) +_DICT = _const(16) + + +def _container_type(obj): + typ = type(obj) + if issubclass(typ, list): + return _LIST | _SIMPLE + if issubclass(typ, set): + return _SET | _SIMPLE + if issubclass(typ, dict): + return _DICT + if issubclass(typ, tuple): + return _TUPLE | _SIMPLE + return _NONCONTAINER + + +def _pprint_simple_container(obj, type_code, stream, indent, sort_dicts): + if type_code & _LIST: + chars = "[]" + elif type_code & _TUPLE: + chars = "()" + else: # type_code & _SET: + chars = "{}" + + stream.write(chars[0] + " " * (indent - 1)) + first = True + for item in obj: + if not first: + stream.write(",\n" + " " * indent) + first = False + # TODO: would recurse here + stream.write(repr(item)) + stream.write(chars[1]) + + +def _pprint_dict(obj, type_code, stream, indent, sort_dicts): + stream.write("{" + " " * (indent - 1)) + first = True + items = obj.items() + if sort_dicts: + items = sorted(items, key=lambda x: x[0]) + for key, val in items: + if not first: + stream.write(",\n" + " " * indent) + first = False + # TODO: would recurse here + stream.write(repr(key)) + stream.write(": ") + stream.write(repr(val)) + stream.write("}") From f9f02a31e206ad9d5e8ff880c9e62e14e31cecea Mon Sep 17 00:00:00 2001 From: Michael Bentley Date: Thu, 16 Sep 2021 11:11:51 -0600 Subject: [PATCH 3/7] pprint: Implement depth recursion. --- python-stdlib/pprint/pprint.py | 128 ++++++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index 8ecff3806..b8f1dd8d3 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -1,6 +1,10 @@ -from io import StringIO as _StringIO -from sys import stdout as _stdout -from machine import const as _const +import io as _io +import sys as _sys + +try: + from micropython import const as _const +except ImportError: + _const = lambda x: x class PrettyPrinter: @@ -10,27 +14,34 @@ class PrettyPrinter: call. For parameter descriptions, look at pprint() documentation. """ - def __init__(self, indent=1, stream=None, sort_dicts=True): - self.indent = indent - self.stream = stream if stream else _stdout - self.sort_dicts = sort_dicts + def __init__(self, indent=1, depth=1, stream=None, sort_dicts=True): + self._indent = indent + self._depth = depth + self._stream = stream if stream else _sys.stdout + self._sort_dicts = sort_dicts def pformat(self, obj): """Call pformat() function with cached constructor values""" - return pformat(obj, indent=self.indent, sort_dicts=self.sort_dicts) + return pformat(obj, indent=self._indent, depth=self._depth, sort_dicts=self._sort_dicts) def pprint(self, obj): """Call pprint() function with cached constructor values""" - pprint(obj, stream=self.stream, indent=self.indent, sort_dicts=self.sort_dicts) + pprint( + obj, + stream=self._stream, + indent=self._indent, + depth=self._depth, + sort_dicts=self._sort_dicts, + ) -def pformat(obj, indent=1, sort_dicts=True): +def pformat(obj, indent=1, depth=1, sort_dicts=True): """Simply calls pprint() but capturing prints into a string and returning. Does not add a newline to the end like pprint() does. """ - stream = _StringIO() - _pprint_impl(obj, stream=stream, indent=indent, sort_dicts=sort_dicts) + stream = _io.StringIO() + _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) return stream.getvalue() @@ -39,7 +50,7 @@ def pp(obj, *args, sort_dicts=False, **kwargs): pprint(obj, *args, sort_dicts=sort_dicts, **kwargs) -def pprint(obj, stream=None, indent=1, sort_dicts=True): +def pprint(obj, stream=None, indent=1, depth=1, sort_dicts=True): """Simple implementation of a pretty-printer. For simplicity, this does not recurse down. It does not produce the same @@ -49,6 +60,14 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True): Only expands containers. Currently supported containers are set, list, dict, and tuple. + The depth parameter defaults to 1 which is different from the CPython + implementation which defaults to None, meaning infinite recursion. This + implementation is significantly simpler and does not have safety checks + against recursive structures (i.e., structures that contain a copy of + themselves somewhere) or costly overexpansion. This simplicity gains speed + and memory efficiency at the potential cost of expanding too far. Increase + the depth parameter cautiously. + stream The desired output stream. If omitted (or False), the standard output stream will be used. @@ -56,6 +75,11 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True): indent Number of space to indent for each level of nesting. + depth + The maximum depth to print out nested structures. Set to None for + infinite depth. Warning: be careful of infinite depth as infinite + recursion and overexpansion checks are not performed. + sort_dicts If true, dict keys are sorted. You must ensure that the dicts encountered can be sorted against each other (for example, int cannot @@ -65,8 +89,8 @@ def pprint(obj, stream=None, indent=1, sort_dicts=True): mismatch """ if not stream: - stream = _stdout - _pprint_impl(obj, stream, indent, sort_dicts) + stream = _sys.stdout + _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) stream.write("\n") # end in a newline @@ -85,16 +109,36 @@ def ppdir(obj, *args, hidden=False, **kwargs): # Private implementation -def _pprint_impl(obj, stream, indent, sort_dicts): +def _pprint_impl(obj, stream, indent, sort_dicts, maxlevel, level, indent_per_level): type_code = _container_type(obj) - if type_code != _NONCONTAINER and len(obj) > 1: + if (maxlevel is not None and level >= maxlevel) or type_code == _NONCONTAINER or not obj: + # directly print noncontainers, empty containers, or if we have reached + # the max level. + stream.write(repr(obj)) + else: + # expand the container if type_code & _SIMPLE: - _pprint_simple_container(obj, type_code, stream, indent, sort_dicts) + _pprint_simple_container( + obj, + type_code, + stream, + indent + indent_per_level, + sort_dicts, + maxlevel, + level + 1, + indent_per_level, + ) else: # type_code & _DICT: - _pprint_dict(obj, type_code, stream, indent, sort_dicts) - else: - # directly print noncontainers or containers with one or fewer elements - stream.write(repr(obj)) + _pprint_dict( + obj, + type_code, + stream, + indent + indent_per_level, + sort_dicts, + maxlevel, + level + 1, + indent_per_level, + ) _NONCONTAINER = _const(0) @@ -118,7 +162,9 @@ def _container_type(obj): return _NONCONTAINER -def _pprint_simple_container(obj, type_code, stream, indent, sort_dicts): +def _pprint_simple_container( + obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level +): if type_code & _LIST: chars = "[]" elif type_code & _TUPLE: @@ -126,29 +172,45 @@ def _pprint_simple_container(obj, type_code, stream, indent, sort_dicts): else: # type_code & _SET: chars = "{}" - stream.write(chars[0] + " " * (indent - 1)) + indent_string = " " * indent + + stream.write(chars[0]) + stream.write(" " * (indent_per_level - 1)) first = True for item in obj: if not first: - stream.write(",\n" + " " * indent) + stream.write(",\n") + stream.write(indent_string) first = False - # TODO: would recurse here - stream.write(repr(item)) + # recurse here + _pprint_impl(item, stream, indent, sort_dicts, maxlevel, level, indent_per_level) + if type_code & _TUPLE and len(obj) == 1: + stream.write(",") stream.write(chars[1]) -def _pprint_dict(obj, type_code, stream, indent, sort_dicts): - stream.write("{" + " " * (indent - 1)) +def _pprint_dict(obj, type_code, stream, indent, sort_dicts, maxlevel, level, indent_per_level): + indent_string = " " * indent + stream.write("{") + stream.write(" " * (indent_per_level - 1)) first = True items = obj.items() if sort_dicts: items = sorted(items, key=lambda x: x[0]) for key, val in items: if not first: - stream.write(",\n" + " " * indent) + stream.write(",\n") + stream.write(indent_string) first = False - # TODO: would recurse here - stream.write(repr(key)) + # python's implementation does not multiline keys, then offsets the + # value by the key's length. They still do safety checks such as + # recursiveness and checking that it can be printed within the + # specified width, which we skip in this implementation. + keystr = repr(key) + stream.write(keystr) stream.write(": ") - stream.write(repr(val)) + # recurse on the value, lined up to wrap in front of the colon. + _pprint_impl( + val, stream, indent + len(keystr) + 2, sort_dicts, maxlevel, level, indent_per_level + ) stream.write("}") From 61b4317dcda098f0daafac60d9a7b9ac111740c1 Mon Sep 17 00:00:00 2001 From: Michael Bentley Date: Thu, 16 Sep 2021 11:13:47 -0600 Subject: [PATCH 4/7] pprint: Add test_pprint.py. --- python-stdlib/pprint/pprint.py | 10 +- python-stdlib/pprint/test_pprint.py | 481 ++++++++++++++++++++++++++++ 2 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 python-stdlib/pprint/test_pprint.py diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index b8f1dd8d3..61b5d782b 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -53,9 +53,13 @@ def pp(obj, *args, sort_dicts=False, **kwargs): def pprint(obj, stream=None, indent=1, depth=1, sort_dicts=True): """Simple implementation of a pretty-printer. - For simplicity, this does not recurse down. It does not produce the same - output as the CPython implementation. Instead, it is intended to simply - expand lists and dictionaries to multiple lines for easy viewing. + It does not produce the same output as the CPython implementation, but it's + close. Instead, it is intended to simply expand lists and dictionaries to + multiple lines for easy viewing. When the recursion depth has been + reached, the elements will be printed with repl(item) instead of the + CPython implementation of eliding the results (e.g., "{...}"). This is + because this implementation is not as safe as the CPython implementation + for deep recursion, which is detailed two paragraphs lower. Only expands containers. Currently supported containers are set, list, dict, and tuple. diff --git a/python-stdlib/pprint/test_pprint.py b/python-stdlib/pprint/test_pprint.py new file mode 100644 index 000000000..2256cd4f5 --- /dev/null +++ b/python-stdlib/pprint/test_pprint.py @@ -0,0 +1,481 @@ +import pprint +from io import StringIO +import sys + +# setup + + +def pformat_as_pprint(*args, stream=None, **kwargs): + """Use pformat() to provide pprint() interface""" + val = pprint.pformat(*args, **kwargs) + if not stream: + stream = sys.stdout + stream.write(val) + stream.write("\n") + + +def pp_as_pprint(*args, sort_dicts=True, **kwargs): + """Use pp() to provide pprint() interface""" + pprint.pp(*args, sort_dicts=sort_dicts, **kwargs) + + +def PrettyPrinter_pprint_as_pprint(obj, *args, **kwargs): + """Use PrettyPrinter.pprint() to provide pprint() interface""" + p = pprint.PrettyPrinter(*args, **kwargs) + p.pprint(obj) + + +def PrettyPrinter_pformat_as_pprint(obj, *args, **kwargs): + """Use PrettyPrinter.pformat() to provide pprint() interface""" + p = pprint.PrettyPrinter(*args, **kwargs) + val = p.pformat(obj) + p._stream.write(val) + p._stream.write("\n") + + +pprint_func_map = { + "pprint": pprint.pprint, + "pformat": pformat_as_pprint, + "pp": pp_as_pprint, + "PrettyPrinter.pprint": PrettyPrinter_pprint_as_pprint, + "PrettyPrinter.pformat": PrettyPrinter_pformat_as_pprint, +} + + +def pprint_func_to_pformat_func(pprint_func): + """Convert pprint() interface into the pformat() interface""" + + def pformat_func(*args, **kwargs): + sio = StringIO() + pprint_func(*args, stream=sio, **kwargs) + return sio.getvalue()[:-1] # return all but the trailing newline + + return pformat_func + + +pformat_func_map = {key: pprint_func_to_pformat_func(val) for key, val in pprint_func_map.items()} +# reset the pformat function instead of two indirect wrappers +pformat_func_map["pformat"] = pprint.pformat + + +# test functions + + +def test_stream(name, pprint_func): + # TODO: somehow test that stream=None goes to stdout + # This method of mocking stdout works in CPython, but not Micropython + # try: + # # mock standard out + # oldstdout = sys.stdout + # sys.stdout = sio + # # run test + # #... + # finally: + # # restore standard out + # sys.stdout = oldstdout + + sio = StringIO() + sio2 = StringIO() + gen_msg = lambda s: f"{name}: {repr(s.getvalue())}" + pprint_func("abcdefg", stream=sio2) + assert sio.getvalue() == "", gen_msg(sio) + assert sio2.getvalue() == "'abcdefg'\n", gen_msg(sio2) + pprint_func("zxcvbnm", stream=sio) + assert sio.getvalue() == "'zxcvbnm'\n", gen_msg(sio) + assert sio2.getvalue() == "'abcdefg'\n", gen_msg(sio2) + + +def test_non_containers(name, pformat_func): + class A: + def __repr__(self): + return "\n\ncocoa\n\nnuts\n\n" + + assert pformat_func(1) == repr(1), f"{name}, {pformat_func(1)}" + assert pformat_func(1.423) == repr(1.423), name + assert pformat_func("hello") == repr("hello"), name + assert pformat_func(b"my grail diary") == repr(b"my grail diary"), name + assert pformat_func(A()) == repr(A()), name + + +def test_empty_containers(name, pformat_func): + assert pformat_func(list()) == "[]", name + assert pformat_func(tuple()) == "()", name + assert pformat_func(set()) == "set()", name + assert pformat_func(dict()) == "{}", name + + +def test_containers_with_one_item(name, pformat_func): + assert pformat_func(["abc"]) == repr(["abc"]), name + assert pformat_func({123}) == repr({123}), name + assert pformat_func({"abc": 123}) == repr({"abc": 123}), name + assert pformat_func(("my tuple",)) == repr(("my tuple",)), name + + +X = { + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500, + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center", + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;", + }, + }, + "name": "King Arthur", +} + +expected_X_depth0_indent0 = repr(X) +expected_X_depth0_indent1 = expected_X_depth0_indent0 +expected_X_depth0_indent2 = expected_X_depth0_indent0 +expected_X_depth0_indent3 = expected_X_depth0_indent0 + +# note: these are sorted by key and will be tested that way +expected_X_depth1_indent0 = ( + "{'name': " + repr(X["name"]) + ",\n" + "'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent1 = ( + "{'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent2 = ( + "{ 'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) +expected_X_depth1_indent3 = ( + "{ 'name': " + repr(X["name"]) + ",\n" + " 'widget': " + repr(X["widget"]) + "}" +) + +expected_X_depth2_indent0 = ( + "{'name': 'King Arthur',\n" + "'widget': {'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent1 = ( + "{'name': 'King Arthur',\n" + " 'widget': {'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent2 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) +expected_X_depth2_indent3 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': " + + repr(X["widget"]["debug"]) + + ",\n" + + " 'image': " + + repr(X["widget"]["image"]) + + ",\n" + + " 'text': " + + repr(X["widget"]["text"]) + + ",\n" + + " 'window': " + + repr(X["widget"]["window"]) + + "}}" +) + +expected_X_depth3_indent0 = ( + "{'name': 'King Arthur',\n" + "'widget': {'debug': 'on',\n" + " 'image': {'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': {'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': {'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent1 = ( + "{'name': 'King Arthur',\n" + " 'widget': {'debug': 'on',\n" + " 'image': {'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': {'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': {'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent2 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': 'on',\n" + " 'image': { 'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': { 'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': { 'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) +expected_X_depth3_indent3 = ( + "{ 'name': 'King Arthur',\n" + " 'widget': { 'debug': 'on',\n" + " 'image': { 'alignment': " + + repr(X["widget"]["image"]["alignment"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["image"]["hOffset"]) + + ",\n" + " 'name': " + repr(X["widget"]["image"]["name"]) + ",\n" + " 'src': " + repr(X["widget"]["image"]["src"]) + ",\n" + " 'vOffset': " + repr(X["widget"]["image"]["vOffset"]) + "},\n" + " 'text': { 'alignment': " + + repr(X["widget"]["text"]["alignment"]) + + ",\n" + + " 'data': " + + repr(X["widget"]["text"]["data"]) + + ",\n" + + " 'hOffset': " + + repr(X["widget"]["text"]["hOffset"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["text"]["name"]) + + ",\n" + + " 'onMouseUp': " + + repr(X["widget"]["text"]["onMouseUp"]) + + ",\n" + + " 'size': " + + repr(X["widget"]["text"]["size"]) + + ",\n" + + " 'style': " + + repr(X["widget"]["text"]["style"]) + + ",\n" + + " 'vOffset': " + + repr(X["widget"]["text"]["vOffset"]) + + "},\n" + + " 'window': { 'height': " + + repr(X["widget"]["window"]["height"]) + + ",\n" + + " 'name': " + + repr(X["widget"]["window"]["name"]) + + ",\n" + + " 'title': " + + repr(X["widget"]["window"]["title"]) + + ",\n" + + " 'width': " + + repr(X["widget"]["window"]["width"]) + + "}}}" +) + +expected_X_depth4_indent0 = expected_X_depth3_indent0 +expected_X_depth4_indent1 = expected_X_depth3_indent1 +expected_X_depth4_indent2 = expected_X_depth3_indent2 +expected_X_depth4_indent3 = expected_X_depth3_indent3 + +expected_X_depth_None_indent0 = expected_X_depth3_indent0 +expected_X_depth_None_indent1 = expected_X_depth3_indent1 +expected_X_depth_None_indent2 = expected_X_depth3_indent2 +expected_X_depth_None_indent3 = expected_X_depth3_indent3 + + +def test_depth_and_indent(name, pformat_func): + gen_msg = lambda d, i: f"{name} depth={d} indent={i}:\n{pformat_func(X, depth=d, indent=i)}\n" + + assert pformat_func(X, depth=0, indent=0) == expected_X_depth0_indent0, gen_msg(0, 0) + assert pformat_func(X, depth=0, indent=1) == expected_X_depth0_indent1, gen_msg(0, 1) + assert pformat_func(X, depth=0, indent=2) == expected_X_depth0_indent2, gen_msg(0, 3) + assert pformat_func(X, depth=0, indent=3) == expected_X_depth0_indent3, gen_msg(0, 3) + + assert pformat_func(X, depth=1, indent=0) == expected_X_depth1_indent0, gen_msg(1, 0) + assert pformat_func(X, depth=1, indent=1) == expected_X_depth1_indent1, gen_msg(1, 1) + assert pformat_func(X, depth=1, indent=2) == expected_X_depth1_indent2, gen_msg(1, 3) + assert pformat_func(X, depth=1, indent=3) == expected_X_depth1_indent3, gen_msg(1, 3) + + assert pformat_func(X, depth=2, indent=0) == expected_X_depth2_indent0, gen_msg(2, 0) + assert pformat_func(X, depth=2, indent=1) == expected_X_depth2_indent1, gen_msg(2, 1) + assert pformat_func(X, depth=2, indent=2) == expected_X_depth2_indent2, gen_msg(2, 3) + assert pformat_func(X, depth=2, indent=3) == expected_X_depth2_indent3, gen_msg(2, 3) + + assert pformat_func(X, depth=3, indent=0) == expected_X_depth3_indent0, gen_msg(3, 0) + assert pformat_func(X, depth=3, indent=1) == expected_X_depth3_indent1, gen_msg(3, 1) + assert pformat_func(X, depth=3, indent=2) == expected_X_depth3_indent2, gen_msg(3, 3) + assert pformat_func(X, depth=3, indent=3) == expected_X_depth3_indent3, gen_msg(3, 3) + + assert pformat_func(X, depth=4, indent=0) == expected_X_depth4_indent0, gen_msg(4, 0) + assert pformat_func(X, depth=4, indent=1) == expected_X_depth4_indent1, gen_msg(4, 1) + assert pformat_func(X, depth=4, indent=2) == expected_X_depth4_indent2, gen_msg(4, 3) + assert pformat_func(X, depth=4, indent=3) == expected_X_depth4_indent3, gen_msg(4, 3) + + assert pformat_func(X, depth=None, indent=0) == expected_X_depth_None_indent0, gen_msg(None, 0) + assert pformat_func(X, depth=None, indent=1) == expected_X_depth_None_indent1, gen_msg(None, 1) + assert pformat_func(X, depth=None, indent=2) == expected_X_depth_None_indent2, gen_msg(None, 3) + assert pformat_func(X, depth=None, indent=3) == expected_X_depth_None_indent3, gen_msg(None, 3) + + +# TODO: it would be good to test with sort_dicts=False + + +# run tests + +for name, func in pprint_func_map.items(): + test_stream(name, func) + +for name, func in pformat_func_map.items(): + test_non_containers(name, func) + test_empty_containers(name, func) + test_depth_and_indent(name, func) + +# +# def test_depth(pformat_func): +# From 3163649dea2c48028a24f6721d796df92ab778f1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 11 Jul 2026 17:45:24 +1000 Subject: [PATCH 5/7] pprint: Fix use of micropython.const. It won't work when imported as `_const`. Also use non-underscore names for `io` and `sys` modules to reduce code size (having the underscore requires a separate qstr). Signed-off-by: Damien George --- python-stdlib/pprint/pprint.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/python-stdlib/pprint/pprint.py b/python-stdlib/pprint/pprint.py index 61b5d782b..5cfd52260 100644 --- a/python-stdlib/pprint/pprint.py +++ b/python-stdlib/pprint/pprint.py @@ -1,10 +1,13 @@ -import io as _io -import sys as _sys +import io +import sys +from micropython import const -try: - from micropython import const as _const -except ImportError: - _const = lambda x: x +_NONCONTAINER = const(0) +_SIMPLE = const(1) +_LIST = const(2) +_SET = const(4) +_TUPLE = const(8) +_DICT = const(16) class PrettyPrinter: @@ -17,7 +20,7 @@ class PrettyPrinter: def __init__(self, indent=1, depth=1, stream=None, sort_dicts=True): self._indent = indent self._depth = depth - self._stream = stream if stream else _sys.stdout + self._stream = stream if stream else sys.stdout self._sort_dicts = sort_dicts def pformat(self, obj): @@ -40,7 +43,7 @@ def pformat(obj, indent=1, depth=1, sort_dicts=True): Does not add a newline to the end like pprint() does. """ - stream = _io.StringIO() + stream = io.StringIO() _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) return stream.getvalue() @@ -93,7 +96,7 @@ def pprint(obj, stream=None, indent=1, depth=1, sort_dicts=True): mismatch """ if not stream: - stream = _sys.stdout + stream = sys.stdout _pprint_impl(obj, stream, 0, sort_dicts, depth, 0, indent) stream.write("\n") # end in a newline @@ -145,14 +148,6 @@ def _pprint_impl(obj, stream, indent, sort_dicts, maxlevel, level, indent_per_le ) -_NONCONTAINER = _const(0) -_SIMPLE = _const(1) -_LIST = _const(2) -_SET = _const(4) -_TUPLE = _const(8) -_DICT = _const(16) - - def _container_type(obj): typ = type(obj) if issubclass(typ, list): From 920c9b1932f33548b441479f6147ddd0dcf758d9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 11 Jul 2026 17:47:55 +1000 Subject: [PATCH 6/7] pprint: Bump version to 0.1.0. Lots of features were added in parent commits. Signed-off-by: Damien George --- python-stdlib/pprint/manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-stdlib/pprint/manifest.py b/python-stdlib/pprint/manifest.py index 9c0ebe9ed..7bd2c3749 100644 --- a/python-stdlib/pprint/manifest.py +++ b/python-stdlib/pprint/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.0.4") +metadata(version="0.1.0") module("pprint.py") From 6cb320b3d8feac6fdc851769b1b9ae906f4f7022 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 11 Jul 2026 17:56:57 +1000 Subject: [PATCH 7/7] tools/ci.sh: Add pprint test to CI. Signed-off-by: Damien George --- tools/ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ci.sh b/tools/ci.sh index e2b6919be..2040185c0 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -71,6 +71,7 @@ function ci_package_tests_run { python-stdlib/operator/test_operator.py \ python-stdlib/os-path/test_path.py \ python-stdlib/pickle/test_pickle.py \ + python-stdlib/pprint/test_pprint.py \ python-stdlib/string/test_translate.py \ python-stdlib/unittest/tests/exception.py \ unix-ffi/gettext/test_gettext.py \