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.
3 from click.testing import CliRunner
4 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
16 from tests.util import DATA_DIR
18 pytestmark = pytest.mark.jupyter
19 pytest.importorskip("IPython", reason="IPython is an optional dependency")
20 pytest.importorskip("tokenize_rt", reason="tokenize-rt is an optional dependency")
22 JUPYTER_MODE = Mode(is_ipynb=True)
27 def test_noop() -> None:
29 with pytest.raises(NothingChanged):
30 format_cell(src, fast=True, mode=JUPYTER_MODE)
33 @pytest.mark.parametrize("fast", [True, False])
34 def test_trailing_semicolon(fast: bool) -> None:
36 result = format_cell(src, fast=fast, mode=JUPYTER_MODE)
37 expected = 'foo = "a";'
38 assert result == expected
41 def test_trailing_semicolon_with_comment() -> None:
42 src = 'foo = "a" ; # bar'
43 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
44 expected = 'foo = "a"; # bar'
45 assert result == expected
48 def test_trailing_semicolon_with_comment_on_next_line() -> None:
49 src = "import black;\n\n# this is a comment"
50 with pytest.raises(NothingChanged):
51 format_cell(src, fast=True, mode=JUPYTER_MODE)
54 def test_trailing_semicolon_indented() -> None:
55 src = "with foo:\n plot_bar();"
56 with pytest.raises(NothingChanged):
57 format_cell(src, fast=True, mode=JUPYTER_MODE)
60 def test_trailing_semicolon_noop() -> None:
62 with pytest.raises(NothingChanged):
63 format_cell(src, fast=True, mode=JUPYTER_MODE)
66 def test_cell_magic() -> None:
67 src = "%%time\nfoo =bar"
68 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
69 expected = "%%time\nfoo = bar"
70 assert result == expected
73 def test_cell_magic_noop() -> None:
75 with pytest.raises(NothingChanged):
76 format_cell(src, fast=True, mode=JUPYTER_MODE)
79 @pytest.mark.parametrize(
82 pytest.param("ls =!ls", "ls = !ls", id="System assignment"),
83 pytest.param("!ls\n'foo'", '!ls\n"foo"', id="System call"),
84 pytest.param("!!ls\n'foo'", '!!ls\n"foo"', id="Other system call"),
85 pytest.param("?str\n'foo'", '?str\n"foo"', id="Help"),
86 pytest.param("??str\n'foo'", '??str\n"foo"', id="Other help"),
88 "%matplotlib inline\n'foo'",
89 '%matplotlib inline\n"foo"',
90 id="Line magic with argument",
92 pytest.param("%time\n'foo'", '%time\n"foo"', id="Line magic without argument"),
94 "env = %env var", "env = %env var", id="Assignment to environment variable"
96 pytest.param("env = %env", "env = %env", id="Assignment to magic"),
99 def test_magic(src: str, expected: str) -> None:
100 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
101 assert result == expected
104 @pytest.mark.parametrize(
108 "%%html --isolated\n2+2",
111 def test_non_python_magics(src: str) -> None:
112 with pytest.raises(NothingChanged):
113 format_cell(src, fast=True, mode=JUPYTER_MODE)
116 def test_set_input() -> None:
118 with pytest.raises(NothingChanged):
119 format_cell(src, fast=True, mode=JUPYTER_MODE)
122 def test_input_already_contains_transformed_magic() -> None:
123 src = '%time foo()\nget_ipython().run_cell_magic("time", "", "foo()\\n")'
124 with pytest.raises(NothingChanged):
125 format_cell(src, fast=True, mode=JUPYTER_MODE)
128 def test_magic_noop() -> None:
130 with pytest.raises(NothingChanged):
131 format_cell(src, fast=True, mode=JUPYTER_MODE)
134 def test_cell_magic_with_magic() -> None:
135 src = "%%t -n1\nls =!ls"
136 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
137 expected = "%%t -n1\nls = !ls"
138 assert result == expected
141 def test_cell_magic_nested() -> None:
142 src = "%%time\n%%time\n2+2"
143 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
144 expected = "%%time\n%%time\n2 + 2"
145 assert result == expected
148 def test_cell_magic_with_magic_noop() -> None:
149 src = "%%t -n1\nls = !ls"
150 with pytest.raises(NothingChanged):
151 format_cell(src, fast=True, mode=JUPYTER_MODE)
154 def test_automagic() -> None:
155 src = "pip install black"
156 with pytest.raises(NothingChanged):
157 format_cell(src, fast=True, mode=JUPYTER_MODE)
160 def test_multiline_magic() -> None:
161 src = "%time 1 + \\\n2"
162 with pytest.raises(NothingChanged):
163 format_cell(src, fast=True, mode=JUPYTER_MODE)
166 def test_multiline_no_magic() -> None:
168 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
170 assert result == expected
173 def test_cell_magic_with_invalid_body() -> None:
174 src = "%%time\nif True"
175 with pytest.raises(NothingChanged):
176 format_cell(src, fast=True, mode=JUPYTER_MODE)
179 def test_empty_cell() -> None:
181 with pytest.raises(NothingChanged):
182 format_cell(src, fast=True, mode=JUPYTER_MODE)
185 def test_entire_notebook_empty_metadata() -> None:
186 with open(DATA_DIR / "notebook_empty_metadata.ipynb", "rb") as fd:
187 content_bytes = fd.read()
188 content = content_bytes.decode()
189 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
194 ' "cell_type": "code",\n'
195 ' "execution_count": null,\n'
203 ' "print(\\"foo\\")"\n'
207 ' "cell_type": "code",\n'
208 ' "execution_count": null,\n'
216 ' "nbformat_minor": 4\n'
219 assert result == expected
222 def test_entire_notebook_trailing_newline() -> None:
223 with open(DATA_DIR / "notebook_trailing_newline.ipynb", "rb") as fd:
224 content_bytes = fd.read()
225 content = content_bytes.decode()
226 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
231 ' "cell_type": "code",\n'
232 ' "execution_count": null,\n'
240 ' "print(\\"foo\\")"\n'
244 ' "cell_type": "code",\n'
245 ' "execution_count": null,\n'
252 ' "interpreter": {\n'
253 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa:B950
256 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
257 ' "name": "python3"\n'
259 ' "language_info": {\n'
260 ' "name": "python",\n'
265 ' "nbformat_minor": 4\n'
268 assert result == expected
271 def test_entire_notebook_no_trailing_newline() -> None:
272 with open(DATA_DIR / "notebook_no_trailing_newline.ipynb", "rb") as fd:
273 content_bytes = fd.read()
274 content = content_bytes.decode()
275 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
280 ' "cell_type": "code",\n'
281 ' "execution_count": null,\n'
289 ' "print(\\"foo\\")"\n'
293 ' "cell_type": "code",\n'
294 ' "execution_count": null,\n'
301 ' "interpreter": {\n'
302 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa: B950
305 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
306 ' "name": "python3"\n'
308 ' "language_info": {\n'
309 ' "name": "python",\n'
314 ' "nbformat_minor": 4\n'
317 assert result == expected
320 def test_entire_notebook_without_changes() -> None:
321 with open(DATA_DIR / "notebook_without_changes.ipynb", "rb") as fd:
322 content_bytes = fd.read()
323 content = content_bytes.decode()
324 with pytest.raises(NothingChanged):
325 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
328 def test_non_python_notebook() -> None:
329 with open(DATA_DIR / "non_python_notebook.ipynb", "rb") as fd:
330 content_bytes = fd.read()
331 content = content_bytes.decode()
332 with pytest.raises(NothingChanged):
333 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
336 def test_empty_string() -> None:
337 with pytest.raises(NothingChanged):
338 format_file_contents("", fast=True, mode=JUPYTER_MODE)
341 def test_unparseable_notebook() -> None:
342 path = DATA_DIR / "notebook_which_cant_be_parsed.ipynb"
343 msg = rf"File '{re.escape(str(path))}' cannot be parsed as valid Jupyter notebook\."
344 with pytest.raises(ValueError, match=msg):
345 format_file_in_place(path, fast=True, mode=JUPYTER_MODE)
348 def test_ipynb_diff_with_change() -> None:
349 result = runner.invoke(
352 str(DATA_DIR / "notebook_trailing_newline.ipynb"),
356 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
357 assert expected in result.output
360 def test_ipynb_diff_with_no_change() -> None:
361 result = runner.invoke(
364 str(DATA_DIR / "notebook_without_changes.ipynb"),
368 expected = "1 file would be left unchanged."
369 assert expected in result.output
372 def test_cache_isnt_written_if_no_jupyter_deps_single(
373 monkeypatch: MonkeyPatch, tmpdir: local
375 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
376 jupyter_dependencies_are_installed.cache_clear()
377 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
378 tmp_nb = tmpdir / "notebook.ipynb"
379 with open(nb) as src, open(tmp_nb, "w") as dst:
380 dst.write(src.read())
382 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
384 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
385 assert "No Python files are present to be formatted. Nothing to do" in result.output
386 jupyter_dependencies_are_installed.cache_clear()
388 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
390 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
391 assert "reformatted" in result.output
394 def test_cache_isnt_written_if_no_jupyter_deps_dir(
395 monkeypatch: MonkeyPatch, tmpdir: local
397 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
398 jupyter_dependencies_are_installed.cache_clear()
399 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
400 tmp_nb = tmpdir / "notebook.ipynb"
401 with open(nb) as src, open(tmp_nb, "w") as dst:
402 dst.write(src.read())
404 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
406 result = runner.invoke(main, [str(tmpdir)])
407 assert "No Python files are present to be formatted. Nothing to do" in result.output
408 jupyter_dependencies_are_installed.cache_clear()
410 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
412 result = runner.invoke(main, [str(tmpdir)])
413 assert "reformatted" in result.output
416 def test_ipynb_flag(tmpdir: local) -> None:
417 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
418 tmp_nb = tmpdir / "notebook.a_file_extension_which_is_definitely_not_ipynb"
419 with open(nb) as src, open(tmp_nb, "w") as dst:
420 dst.write(src.read())
421 result = runner.invoke(
429 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
430 assert expected in result.output
433 def test_ipynb_and_pyi_flags() -> None:
434 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
435 result = runner.invoke(
444 assert isinstance(result.exception, SystemExit)
445 expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
446 assert result.output == expected
449 def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None:
450 src = "%%time\na = 'foo'"
451 monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo")
453 AssertionError, match="Black was not able to replace IPython magic"
455 format_cell(src, fast=True, mode=JUPYTER_MODE)