All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 from click.testing import CliRunner
3 from black.handle_ipynb_magics import jupyter_dependencies_are_installed
13 from black import Mode
14 from _pytest.monkeypatch import MonkeyPatch
15 from py.path import local
17 pytestmark = pytest.mark.jupyter
18 pytest.importorskip("IPython", reason="IPython is an optional dependency")
19 pytest.importorskip("tokenize_rt", reason="tokenize-rt is an optional dependency")
21 JUPYTER_MODE = Mode(is_ipynb=True)
26 def test_noop() -> None:
28 with pytest.raises(NothingChanged):
29 format_cell(src, fast=True, mode=JUPYTER_MODE)
32 @pytest.mark.parametrize("fast", [True, False])
33 def test_trailing_semicolon(fast: bool) -> None:
35 result = format_cell(src, fast=fast, mode=JUPYTER_MODE)
36 expected = 'foo = "a";'
37 assert result == expected
40 def test_trailing_semicolon_with_comment() -> None:
41 src = 'foo = "a" ; # bar'
42 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
43 expected = 'foo = "a"; # bar'
44 assert result == expected
47 def test_trailing_semicolon_with_comment_on_next_line() -> None:
48 src = "import black;\n\n# this is a comment"
49 with pytest.raises(NothingChanged):
50 format_cell(src, fast=True, mode=JUPYTER_MODE)
53 def test_trailing_semicolon_indented() -> None:
54 src = "with foo:\n plot_bar();"
55 with pytest.raises(NothingChanged):
56 format_cell(src, fast=True, mode=JUPYTER_MODE)
59 def test_trailing_semicolon_noop() -> None:
61 with pytest.raises(NothingChanged):
62 format_cell(src, fast=True, mode=JUPYTER_MODE)
65 def test_cell_magic() -> None:
66 src = "%%time\nfoo =bar"
67 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
68 expected = "%%time\nfoo = bar"
69 assert result == expected
72 def test_cell_magic_noop() -> None:
74 with pytest.raises(NothingChanged):
75 format_cell(src, fast=True, mode=JUPYTER_MODE)
78 @pytest.mark.parametrize(
81 pytest.param("ls =!ls", "ls = !ls", id="System assignment"),
82 pytest.param("!ls\n'foo'", '!ls\n"foo"', id="System call"),
83 pytest.param("!!ls\n'foo'", '!!ls\n"foo"', id="Other system call"),
84 pytest.param("?str\n'foo'", '?str\n"foo"', id="Help"),
85 pytest.param("??str\n'foo'", '??str\n"foo"', id="Other help"),
87 "%matplotlib inline\n'foo'",
88 '%matplotlib inline\n"foo"',
89 id="Line magic with argument",
91 pytest.param("%time\n'foo'", '%time\n"foo"', id="Line magic without argument"),
94 def test_magic(src: str, expected: str) -> None:
95 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
96 assert result == expected
99 @pytest.mark.parametrize(
103 "%%html --isolated\n2+2",
106 def test_non_python_magics(src: str) -> None:
107 with pytest.raises(NothingChanged):
108 format_cell(src, fast=True, mode=JUPYTER_MODE)
111 def test_set_input() -> None:
113 with pytest.raises(NothingChanged):
114 format_cell(src, fast=True, mode=JUPYTER_MODE)
117 def test_input_already_contains_transformed_magic() -> None:
118 src = '%time foo()\nget_ipython().run_cell_magic("time", "", "foo()\\n")'
119 with pytest.raises(NothingChanged):
120 format_cell(src, fast=True, mode=JUPYTER_MODE)
123 def test_magic_noop() -> None:
125 with pytest.raises(NothingChanged):
126 format_cell(src, fast=True, mode=JUPYTER_MODE)
129 def test_cell_magic_with_magic() -> None:
130 src = "%%t -n1\nls =!ls"
131 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
132 expected = "%%t -n1\nls = !ls"
133 assert result == expected
136 def test_cell_magic_nested() -> None:
137 src = "%%time\n%%time\n2+2"
138 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
139 expected = "%%time\n%%time\n2 + 2"
140 assert result == expected
143 def test_cell_magic_with_magic_noop() -> None:
144 src = "%%t -n1\nls = !ls"
145 with pytest.raises(NothingChanged):
146 format_cell(src, fast=True, mode=JUPYTER_MODE)
149 def test_automagic() -> None:
150 src = "pip install black"
151 with pytest.raises(NothingChanged):
152 format_cell(src, fast=True, mode=JUPYTER_MODE)
155 def test_multiline_magic() -> None:
156 src = "%time 1 + \\\n2"
157 with pytest.raises(NothingChanged):
158 format_cell(src, fast=True, mode=JUPYTER_MODE)
161 def test_multiline_no_magic() -> None:
163 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
165 assert result == expected
168 def test_cell_magic_with_invalid_body() -> None:
169 src = "%%time\nif True"
170 with pytest.raises(NothingChanged):
171 format_cell(src, fast=True, mode=JUPYTER_MODE)
174 def test_empty_cell() -> None:
176 with pytest.raises(NothingChanged):
177 format_cell(src, fast=True, mode=JUPYTER_MODE)
180 def test_entire_notebook_empty_metadata() -> None:
182 os.path.join("tests", "data", "notebook_empty_metadata.ipynb"), "rb"
184 content_bytes = fd.read()
185 content = content_bytes.decode()
186 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
191 ' "cell_type": "code",\n'
192 ' "execution_count": null,\n'
200 ' "print(\\"foo\\")"\n'
204 ' "cell_type": "code",\n'
205 ' "execution_count": null,\n'
213 ' "nbformat_minor": 4\n'
216 assert result == expected
219 def test_entire_notebook_trailing_newline() -> None:
221 os.path.join("tests", "data", "notebook_trailing_newline.ipynb"), "rb"
223 content_bytes = fd.read()
224 content = content_bytes.decode()
225 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
230 ' "cell_type": "code",\n'
231 ' "execution_count": null,\n'
239 ' "print(\\"foo\\")"\n'
243 ' "cell_type": "code",\n'
244 ' "execution_count": null,\n'
251 ' "interpreter": {\n'
252 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa:B950
255 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
256 ' "name": "python3"\n'
258 ' "language_info": {\n'
259 ' "name": "python",\n'
264 ' "nbformat_minor": 4\n'
267 assert result == expected
270 def test_entire_notebook_no_trailing_newline() -> None:
272 os.path.join("tests", "data", "notebook_no_trailing_newline.ipynb"), "rb"
274 content_bytes = fd.read()
275 content = content_bytes.decode()
276 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
281 ' "cell_type": "code",\n'
282 ' "execution_count": null,\n'
290 ' "print(\\"foo\\")"\n'
294 ' "cell_type": "code",\n'
295 ' "execution_count": null,\n'
302 ' "interpreter": {\n'
303 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa: B950
306 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
307 ' "name": "python3"\n'
309 ' "language_info": {\n'
310 ' "name": "python",\n'
315 ' "nbformat_minor": 4\n'
318 assert result == expected
321 def test_entire_notebook_without_changes() -> None:
323 os.path.join("tests", "data", "notebook_without_changes.ipynb"), "rb"
325 content_bytes = fd.read()
326 content = content_bytes.decode()
327 with pytest.raises(NothingChanged):
328 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
331 def test_non_python_notebook() -> None:
332 with open(os.path.join("tests", "data", "non_python_notebook.ipynb"), "rb") as fd:
333 content_bytes = fd.read()
334 content = content_bytes.decode()
335 with pytest.raises(NothingChanged):
336 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
339 def test_empty_string() -> None:
340 with pytest.raises(NothingChanged):
341 format_file_contents("", fast=True, mode=JUPYTER_MODE)
344 def test_unparseable_notebook() -> None:
346 r"File 'tests[/\\]data[/\\]notebook_which_cant_be_parsed\.ipynb' "
347 r"cannot be parsed as valid Jupyter notebook\."
349 with pytest.raises(ValueError, match=msg):
350 format_file_in_place(
351 pathlib.Path("tests") / "data/notebook_which_cant_be_parsed.ipynb",
357 def test_ipynb_diff_with_change() -> None:
358 result = runner.invoke(
361 os.path.join("tests", "data", "notebook_trailing_newline.ipynb"),
365 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
366 assert expected in result.output
369 def test_ipynb_diff_with_no_change() -> None:
370 result = runner.invoke(
373 os.path.join("tests", "data", "notebook_without_changes.ipynb"),
377 expected = "1 file would be left unchanged."
378 assert expected in result.output
381 def test_cache_isnt_written_if_no_jupyter_deps_single(
382 monkeypatch: MonkeyPatch, tmpdir: local
384 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
385 jupyter_dependencies_are_installed.cache_clear()
386 nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
387 tmp_nb = tmpdir / "notebook.ipynb"
388 with open(nb) as src, open(tmp_nb, "w") as dst:
389 dst.write(src.read())
391 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
393 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
394 assert "No Python files are present to be formatted. Nothing to do" in result.output
395 jupyter_dependencies_are_installed.cache_clear()
397 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
399 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
400 assert "reformatted" in result.output
403 def test_cache_isnt_written_if_no_jupyter_deps_dir(
404 monkeypatch: MonkeyPatch, tmpdir: local
406 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
407 jupyter_dependencies_are_installed.cache_clear()
408 nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
409 tmp_nb = tmpdir / "notebook.ipynb"
410 with open(nb) as src, open(tmp_nb, "w") as dst:
411 dst.write(src.read())
413 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
415 result = runner.invoke(main, [str(tmpdir)])
416 assert "No Python files are present to be formatted. Nothing to do" in result.output
417 jupyter_dependencies_are_installed.cache_clear()
419 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
421 result = runner.invoke(main, [str(tmpdir)])
422 assert "reformatted" in result.output
425 def test_ipynb_flag(tmpdir: local) -> None:
426 nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
427 tmp_nb = tmpdir / "notebook.a_file_extension_which_is_definitely_not_ipynb"
428 with open(nb) as src, open(tmp_nb, "w") as dst:
429 dst.write(src.read())
430 result = runner.invoke(
438 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
439 assert expected in result.output
442 def test_ipynb_and_pyi_flags() -> None:
443 nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
444 result = runner.invoke(
453 assert isinstance(result.exception, SystemExit)
454 expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
455 assert result.output == expected
458 def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None:
459 src = "%%time\na = 'foo'"
460 monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo")
462 AssertionError, match="Black was not able to replace IPython magic"
464 format_cell(src, fast=True, mode=JUPYTER_MODE)