Skip to content

gh-112632 : Added an option for block formatting to pprint #129274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 39 commits into
base: main
Choose a base branch
from

Conversation

StefanTodoran
Copy link

@StefanTodoran StefanTodoran commented Jan 25, 2025

Adds a block print option to pprint. This option is not compatible with compact. Although the original issue only mentions dictionaries, this implementation implements block style for every datastructure supported by pprint. I've been testing it with the code in this gist, which is just a bunch of random datastructures filled with dummy data.

@ghost
Copy link

ghost commented Jan 25, 2025

All commit authors signed the Contributor License Agreement.
CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Jan 25, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@tomasr8 tomasr8 self-requested a review January 25, 2025 09:29
@donBarbos
Copy link
Contributor

looks pretty good 👍, but

  • I think you can move test cases from your gist to Lib/test/test_pprint.py.
  • It is also important to update documentation, you'll find them here Doc/library/pprint.rst.

And please try to make sure CI passes

@bedevere-app
Copy link

bedevere-app bot commented Feb 6, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Feb 6, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Feb 7, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@donBarbos
Copy link
Contributor

Could you also add a News entry into the Misc/NEWS.d (check bedevere's comments)?

And I think it's a full fledged new feature so this update deserves to be in Doc/whatsnew/ (What's New entry)

@StefanTodoran
Copy link
Author

StefanTodoran commented Feb 7, 2025

I've added a test case for the new block_style parameter and updated the documentation. Will add the news entry now. Quick question:

  • How can I make the CI pass? I've only changed Lib.pprint.py and I pass Lib/test/test_pprint.py, so I don't understand why exactly the Tests / Thread sanitizer (free-threading) CI test is failing.

@donBarbos
Copy link
Contributor

donBarbos commented Feb 7, 2025

How can I make the CI pass? I've only changed Lib.pprint.py and I pass Lib/test/test_pprint.py, so I don't understand why exactly the Tests / Thread sanitizer (free-threading) CI test is failing.

sometimes the problem may not be in you and you need to wait and update your branch (at least it helped me)

@donBarbos
Copy link
Contributor

and don't forget to add news to Doc/whatsnew/3.14.rst

And I think it's a full fledged new feature so this update deserves to be in Doc/whatsnew/ (What's New entry)

StefanTodoran and others added 16 commits February 25, 2025 08:47
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
Co-authored-by: donBarbos <donbarbos@proton.me>
@StefanTodoran
Copy link
Author

I've merged those changes. Thanks for helping me out with that! Is there anything else?

@donBarbos
Copy link
Contributor

I've merged those changes. Thanks for helping me out with that! Is there anything else?

Let's try to solve problems with test_pprint.QueryTestCase.test_block_style

Co-authored-by: donBarbos <donbarbos@proton.me>
@donBarbos
Copy link
Contributor

I think we can ping @tomasr8 if you're done making changes.

@StefanTodoran
Copy link
Author

Sounds good, I have no more changes to add.

Copy link
Member

@tomasr8 tomasr8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's looking pretty good now! Just two comments:

stream.write(cls.__name__ + '({')
if self._indent_per_level > 1:
stream.write(self._format_block_start(cls.__name__ + '({', indent))
if self._indent_per_level > 1 and not self._block_style:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not covered by tests (the condition is always False).

For such a large change, I suggest running coverage and checking the html report to make sure your tests cover all cases. There's a guide here, but the gist is:

./python -m ensurepip
./python -m pip install coverage
./python -m coverage run --pylib --source=pprint Lib/test/regrtest.py test_pprint
./python -m coverage html -i --include=`pwd`/Lib/* --omit="Lib/test/*,Lib/*/tests/*"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand, what do you mean the case is not covered? The existing tests already covered the case where _block_style is False and _indent_per_level is >1.

Also when I run the coverage command I don't see anything missing, am I doing something wrong?

python3 -m coverage run --pylib --source=pprint Lib/test/regrtest.py test_pprint
/Users/stodoran/Desktop/cpython/venv/lib/python3.13/site-packages/coverage/inorout.py:486: CoverageWarning: Already imported a file that will be measured: /opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/pprint.py (already-imported)
  self.warn(msg, slug="already-imported")
Using random seed: 4261013096
0:00:00 load avg: 4.87 Run 1 test sequentially in a single process
0:00:00 load avg: 4.87 [1/1] test_pprint

== Tests result: SUCCESS ==

1 test OK.

Total duration: 129 ms
Total tests: run=44
Total test files: run=1/1
Result: SUCCESS
python3 -m coverage html -i --include=`pwd`/Lib/* --omit="Lib/test/*,Lib/*/tests/*"
No data to report.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can see it's not covered:
image

I think there's some issue with the coverage command you ran as it says:

No data to report.

Try running it with the compiled binary directly, not with some other system python.

Copy link
Contributor

@donBarbos donBarbos Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@StefanTodoran if you can't get HTML-based report you can use cli:

$ ./python -m coverage report --show-missing
Name            Stmts   Miss Branch BrPart  Cover   Missing
-----------------------------------------------------------
Lib/pprint.py     476     81    182      7    87%   37-46, 57, 67-72, 77, 82, 87-99, 102, 110, 115-116, 172, 174-177, 182, 185, 189, 192-195, 220, 226, 232, 243-245, 262-264, 276-278, 284-286, 293-295, 312-315, 353->326, 367-369, 388-390, 400-402, 409-411, 424-426, 444, 464, 507, 513, 516, 523, 537-539, 546, 556-558, 560-561, 573-575, 595-597, 600-602, 605-607, 610-612, 694, 703, 717->exit
-----------------------------------------------------------
TOTAL             476     81    182      7    87%

I don't know why on the main branch this line is shown as covered, but this can be solved by adding an explicit pass of indent>1 to the QueryTestCase.test_counter test, like this:

        d = collections.Counter('senselessness')
        self.assertEqual(pprint.pformat(d, indent=2, width=1),
"""\
Counter({ 's': 6,
          'e': 4,
          'n': 2,
          'l': 1})""")

and the line will become covered 👍
(unfortunately i can't send you a suggestion as there is no diff nearby, so please add it yourself)

Comment on lines -252 to +282
stream.write('[')
stream.write(self._format_block_start('[', indent))
self._format_items(object, stream, indent, allowance + 1,
context, level)
stream.write(']')
stream.write(self._format_block_end(']', indent))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a lot of repeated code here conditional on self._block_style. How about defining a context manager to clean this up?

with self._block(stream, indent, '[', ']') as new_indent:
    self._format_items(object, stream, new_indent, allowance + 1,
                       context, level)

@AA-Turner AA-Turner changed the title gh-112632 : Added an option for block formatting to Lib/pprint.py gh-112632 : Added an option for block formatting to pprint Apr 20, 2025
Comment on lines +39 to +40
compact=False, sort_dicts=False, underscore_numbers=False, \
block_style=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are keyword-only (after *), so we have the luxury of alphabetical sorting:

Suggested change
compact=False, sort_dicts=False, underscore_numbers=False, \
block_style=False)
block_style=False, compact=False, sort_dicts=False, \
underscore_numbers=False)

Comment on lines +110 to +111
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise

Suggested change
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
block_style=False, compact=False, \
sort_dicts=True, underscore_numbers=False)

If ``True``,
opening parentheses and brackets will be followed by a newline and the
following content will be indented by one level, similar to block style
JSON formatting. This option is not compatible with *compact*.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is not compatible with compact.

A passing thought: Perhaps introduce a mode (str) argument, and deprecate compact in favour of it? It's not great to have two conflicting Booleans, though it may be the best option for now.

Comment on lines +119 to +120
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise

Suggested change
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
block_style=False, compact=False, \
sort_dicts=True, underscore_numbers=False)

Comment on lines +167 to +168
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise

Suggested change
compact=False, sort_dicts=True, \
underscore_numbers=False, block_style=False)
block_style=False, compact=False, \
sort_dicts=True, underscore_numbers=False)

Comment on lines +117 to +118
compact=False, sort_dicts=True, underscore_numbers=False,
block_style=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
compact=False, sort_dicts=True, underscore_numbers=False,
block_style=False):
block_style=False, compact=False, sort_dicts=True,
underscore_numbers=False):

Comment on lines +146 to +147
pretty-printed json.dumps() when `indent` is supplied. Incompatible
with compact mode.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pretty-printed json.dumps() when `indent` is supplied. Incompatible
with compact mode.
pretty-printed json.dumps() when ``indent`` is supplied.
Incompatible with compact mode.

Comment on lines +223 to +224
else:
return start_str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else:
return start_str
return start_str

Comment on lines +229 to +230
else:
return end_str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else:
return end_str
return end_str

def _pprint_dataclass(self, object, stream, indent, allowance, context, level):
# Lazy import to improve module import time
from dataclasses import fields as dataclass_fields

cls_name = object.__class__.__name__
indent += len(cls_name) + 1
indent += len(cls_name) + 1 if not self._block_style else self._indent_per_level
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep to 79/80 characters (PEP 8)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants