Skip to content

Keep the inserted block indented when the print call is not a bare name - #71

Open
dchaudhari7177 wants to merge 2 commits into
pydantic:mainfrom
dchaudhari7177:fix/indent-print-block-when-call-not-found
Open

Keep the inserted block indented when the print call is not a bare name#71
dchaudhari7177 wants to merge 2 commits into
pydantic:mainfrom
dchaudhari7177:fix/indent-print-block-when-call-not-found

Conversation

@dchaudhari7177

Copy link
Copy Markdown

Closes #59.

Diagnosis

find_print matches only a literal print(...) — an ast.Call whose func is a Name with id 'print'. When it finds nothing, find_print_location falls back to (line_no, 0), and that column becomes the indent of the inserted block.

Output reaches the mock through forms that fall outside that match:

  • builtins.print(...)func is an Attribute
  • a local alias (p = print; p(...)) — func is a Name, but not named print
  • a method that prints internally — the report_default.print() in your example

For those, the multi-line block was written at column 0 inside an indented function body, producing a file that no longer round-trips. Reproduced with line_length=30:

  def main():
      p = print
      p('a' * 30, 'b' * 30)
+"""
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+"""

That explains the "sometimes" in the title. I checked first, and a plain print(...) indents correctly at top level, in def main(), in async def main(), and nested inside an if — being async is not the trigger, and the same mis-indent happens in a sync function.

Fix

The fallback uses the indentation of the source line rather than 0.

That is the correct column whichever form the call took, since the block is inserted directly beneath that line. I preferred it to teaching find_print about Attribute calls: that would fix builtins.print and report.print() but not the alias, and would need extending again for the next form. The AST path is unchanged and still takes precedence.

Tests

Three regressions — builtins.print, an alias, and a method named print — asserting column 4 rather than 0. Both controls matter:

  • a bare print(...) still resolves through the AST path to (2, 4)
  • an out-of-range line_no returns 0 without raising, since the docstring says the argument may be approximate

Revert-verified: the three fail on a clean tree, the two controls pass on both.

Test results

The repo currently has 7 pre-existing failures on main — the ruff 0.16 incompatibility from #69 (which my #70 addresses), plus test_insert_print_check_unchanged[hex_id]. pytest also cannot collect tests/test_update_examples_dir.py or example/test_example.py at all on current pytest, which fails on a generator passed to parametrize.

So rather than quote a total, I diffed the failure sets:

$ diff before.txt after.txt
FAILURE SET IDENTICAL TO BASELINE

Same 12 failures before and after, byte for byte — nothing regressed, and I haven't claimed a green suite that isn't green.

AI disclosure

Written with Claude Code (Claude Opus 5): it found the ast.Name restriction, wrote the fix and the tests, and drafted this description. The reproduction across the four call forms, the revert check, and the before/after failure-set diff were run by hand.

find_print only recognises a literal `print(...)` -- a Call whose func is a
Name with id 'print'. Output reaches the mock through other forms too:
`builtins.print(...)`, a local alias, or a method that prints internally
(the report.print() in the issue). For those, find_print_location fell back
to `(line_no, 0)`, so a multi-line block was written hard against the left
margin even inside an indented function body, and the updated file no
longer round-tripped.

The fallback now uses the indentation of the source line instead of 0. That
is the right answer whichever form the call took, since the block is
inserted directly beneath that line -- and it needs no new special cases as
more callable forms appear.

Three regressions covering builtins.print, an alias and a method named
print, plus controls that a bare print still takes the AST path and that an
out-of-range line_no does not raise, since line_no is documented as
possibly approximate.

Closes pydantic#59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

multi-line prints in async def main are sometimes mis-indented

1 participant