Skip to content

Commit 9485ad4

Browse files
authored
Keep IPython magics in annotated assignments in notebooks (#5628)
IPython transforms an assignment magic with a type annotation the same way as a plain one, so files: list[str] = !ls *.csv becomes `files: list[str] = get_ipython().getoutput('ls *.csv')`. The MagicFinder only looked at `ast.Assign` nodes, so the magic in an annotated assignment was never masked and Black wrote the transformed `get_ipython()` call back into the cell. Look for magics in `ast.AnnAssign` nodes too.
1 parent f74dfd7 commit 9485ad4

3 files changed

Lines changed: 25 additions & 1 deletion

File tree

‎CHANGES.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
line, where it no longer applies (#5618)
6262
- Fix crash when a comment and a `# type: ignore` from the next line are joined onto one
6363
line and ensure the `# type: ignore` comes first (#5625)
64+
- Fix a Jupyter notebook magic in an annotated assignment (for example
65+
`files: list[str] = !ls`) being replaced with the `get_ipython()` call that IPython
66+
runs it as (#5628)
6467

6568
### Preview style
6669

‎src/black/handle_ipynb_magics.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,18 +451,20 @@ class MagicFinder(ast.NodeVisitor):
451451
def __init__(self) -> None:
452452
self.magics: dict[int, list[OffsetAndMagic]] = collections.defaultdict(list)
453453

454-
def visit_Assign(self, node: ast.Assign) -> None:
454+
def visit_Assign(self, node: ast.Assign | ast.AnnAssign) -> None:
455455
"""Look for system assign magics.
456456
457457
For example,
458458
459459
black_version = !black --version
460460
env = %env var
461+
files: list[str] = !ls
461462
462463
would have been (respectively) transformed to
463464
464465
black_version = get_ipython().getoutput('black --version')
465466
env = get_ipython().run_line_magic('env', 'var')
467+
files: list[str] = get_ipython().getoutput('ls')
466468
467469
and we look for instances of any of the latter.
468470
"""
@@ -484,6 +486,10 @@ def visit_Assign(self, node: ast.Assign) -> None:
484486
)
485487
self.generic_visit(node)
486488

489+
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
490+
"""Look for system assign magics with a type annotation."""
491+
self.visit_Assign(node)
492+
487493
def visit_Expr(self, node: ast.Expr) -> None:
488494
"""Look for magics in body of cell.
489495

‎tests/test_ipynb.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ def test_cell_magic_noop() -> None:
144144
id="Assignment after non-ASCII characters",
145145
),
146146
pytest.param("env = %env", "env = %env", id="Assignment to magic"),
147+
pytest.param(
148+
"files: list[str] =!ls",
149+
"files: list[str] = !ls",
150+
id="Annotated system assignment",
151+
),
152+
pytest.param(
153+
"home: str = %env HOME",
154+
"home: str = %env HOME",
155+
id="Annotated assignment to environment variable",
156+
),
147157
),
148158
)
149159
def test_magic(src: str, expected: str, mode: Mode) -> None:
@@ -206,6 +216,11 @@ def test_magic_noop() -> None:
206216
" --format=freeze --disable-pip-version-check",
207217
id="Indented system assignment",
208218
),
219+
pytest.param(
220+
"installed_packages_in_this_environment: list[str] = !pip list"
221+
" --format=freeze --disable-pip-version-check",
222+
id="Annotated system assignment",
223+
),
209224
),
210225
)
211226
def test_long_magic_assignment_is_not_wrapped_in_parentheses(

0 commit comments

Comments
 (0)