Handle circular references while pretty printing
Fixes #157. Formatting a dictionary or other expanded object that points back to itself can exhaust the recursion limit and prevent debug() from showing its contents.
Track object identities only along the active formatting path. Render a repeated ancestor as <Recursion on TYPE>, then continue with the remaining fields. Remove identities in finally, so shared children are formatted normally in each branch and formatting errors do not leave stale state. The same tracking handles reentrant calls from __repr__ without changing the existing stream restoration.
Adds documentation and 12 tests covering self/mutual container cycles, dataclasses with and without slots, __pretty__, a self-yielding generator, reentrant __repr__, repeated shared children, recovery after an exception and debug.format().
Validation on Windows:
- New tests against the unchanged base: 10 failed, 2 passed; with the fix: 12 passed.
- Full suite on Python 3.13.14: 160 passed, 4 skipped, 1 xfailed, 1 xpassed. The original suite had 148 passed with the same remaining statuses.
- Full suite on Python 3.10.20: 160 passed, 4 skipped, 2 xfailed, using the existing frozen lockfile.
- Ruff checks/format, strict basedpyright (with the test virtualenv selected explicitly), documentation examples and strict Zensical build passed. Full diff review and
git diff --checkpassed.
The inherited NO_COLOR environment variable causes the existing test_print_is_tty to fail on both base and patched code. The full-suite results above omit only that variable from the test subprocess environment; repository settings and tests are unchanged. The hosted OS/Python CI matrix remains to be verified.
No new dependencies, credentials, network/API calls in the new tests, or changes to workflows/security checks.
AI disclosure: Codex investigated the issue, implemented the change and tests, reviewed the diff, and ran the validation above.
Summary by cubic
Fixes recursive objects causing an infinite recursion error in debug() and PrettyFormat. Repeated ancestors are now rendered as <Recursion on TYPE> and formatting continues with the remaining fields.
- Tracks object identities only on the active formatting path, using
finallyso shared children format normally in each branch and errors don't leave stale state. - Handles reentrant calls from
__repr__, containers, dataclasses,__pretty__, and generators; values withinsimple_cutoffkeep Python's compact...recursion notation. - Adds 12 tests and documentation examples.