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"),
95 def test_magic(src: str, expected: str) -> None:
96 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
97 assert result == expected
100 @pytest.mark.parametrize(
104 "%%html --isolated\n2+2",
107 def test_non_python_magics(src: str) -> None:
108 with pytest.raises(NothingChanged):
109 format_cell(src, fast=True, mode=JUPYTER_MODE)
112 def test_set_input() -> None:
114 with pytest.raises(NothingChanged):
115 format_cell(src, fast=True, mode=JUPYTER_MODE)
118 def test_input_already_contains_transformed_magic() -> None:
119 src = '%time foo()\nget_ipython().run_cell_magic("time", "", "foo()\\n")'
120 with pytest.raises(NothingChanged):
121 format_cell(src, fast=True, mode=JUPYTER_MODE)
124 def test_magic_noop() -> None:
126 with pytest.raises(NothingChanged):
127 format_cell(src, fast=True, mode=JUPYTER_MODE)
130 def test_cell_magic_with_magic() -> None:
131 src = "%%t -n1\nls =!ls"
132 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
133 expected = "%%t -n1\nls = !ls"
134 assert result == expected
137 def test_cell_magic_nested() -> None:
138 src = "%%time\n%%time\n2+2"
139 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
140 expected = "%%time\n%%time\n2 + 2"
141 assert result == expected
144 def test_cell_magic_with_magic_noop() -> None:
145 src = "%%t -n1\nls = !ls"
146 with pytest.raises(NothingChanged):
147 format_cell(src, fast=True, mode=JUPYTER_MODE)
150 def test_automagic() -> None:
151 src = "pip install black"
152 with pytest.raises(NothingChanged):
153 format_cell(src, fast=True, mode=JUPYTER_MODE)
156 def test_multiline_magic() -> None:
157 src = "%time 1 + \\\n2"
158 with pytest.raises(NothingChanged):
159 format_cell(src, fast=True, mode=JUPYTER_MODE)
162 def test_multiline_no_magic() -> None:
164 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
166 assert result == expected
169 def test_cell_magic_with_invalid_body() -> None:
170 src = "%%time\nif True"
171 with pytest.raises(NothingChanged):
172 format_cell(src, fast=True, mode=JUPYTER_MODE)
175 def test_empty_cell() -> None:
177 with pytest.raises(NothingChanged):
178 format_cell(src, fast=True, mode=JUPYTER_MODE)
181 def test_entire_notebook_empty_metadata() -> None:
182 with open(DATA_DIR / "notebook_empty_metadata.ipynb", "rb") as fd:
183 content_bytes = fd.read()
184 content = content_bytes.decode()
185 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
190 ' "cell_type": "code",\n'
191 ' "execution_count": null,\n'
199 ' "print(\\"foo\\")"\n'
203 ' "cell_type": "code",\n'
204 ' "execution_count": null,\n'
212 ' "nbformat_minor": 4\n'
215 assert result == expected
218 def test_entire_notebook_trailing_newline() -> None:
219 with open(DATA_DIR / "notebook_trailing_newline.ipynb", "rb") as fd:
220 content_bytes = fd.read()
221 content = content_bytes.decode()
222 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
227 ' "cell_type": "code",\n'
228 ' "execution_count": null,\n'
236 ' "print(\\"foo\\")"\n'
240 ' "cell_type": "code",\n'
241 ' "execution_count": null,\n'
248 ' "interpreter": {\n'
249 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa:B950
252 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
253 ' "name": "python3"\n'
255 ' "language_info": {\n'
256 ' "name": "python",\n'
261 ' "nbformat_minor": 4\n'
264 assert result == expected
267 def test_entire_notebook_no_trailing_newline() -> None:
268 with open(DATA_DIR / "notebook_no_trailing_newline.ipynb", "rb") as fd:
269 content_bytes = fd.read()
270 content = content_bytes.decode()
271 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
276 ' "cell_type": "code",\n'
277 ' "execution_count": null,\n'
285 ' "print(\\"foo\\")"\n'
289 ' "cell_type": "code",\n'
290 ' "execution_count": null,\n'
297 ' "interpreter": {\n'
298 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa: B950
301 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
302 ' "name": "python3"\n'
304 ' "language_info": {\n'
305 ' "name": "python",\n'
310 ' "nbformat_minor": 4\n'
313 assert result == expected
316 def test_entire_notebook_without_changes() -> None:
317 with open(DATA_DIR / "notebook_without_changes.ipynb", "rb") as fd:
318 content_bytes = fd.read()
319 content = content_bytes.decode()
320 with pytest.raises(NothingChanged):
321 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
324 def test_non_python_notebook() -> None:
325 with open(DATA_DIR / "non_python_notebook.ipynb", "rb") as fd:
326 content_bytes = fd.read()
327 content = content_bytes.decode()
328 with pytest.raises(NothingChanged):
329 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
332 def test_empty_string() -> None:
333 with pytest.raises(NothingChanged):
334 format_file_contents("", fast=True, mode=JUPYTER_MODE)
337 def test_unparseable_notebook() -> None:
338 path = DATA_DIR / "notebook_which_cant_be_parsed.ipynb"
339 msg = rf"File '{re.escape(str(path))}' cannot be parsed as valid Jupyter notebook\."
340 with pytest.raises(ValueError, match=msg):
341 format_file_in_place(path, fast=True, mode=JUPYTER_MODE)
344 def test_ipynb_diff_with_change() -> None:
345 result = runner.invoke(
348 str(DATA_DIR / "notebook_trailing_newline.ipynb"),
352 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
353 assert expected in result.output
356 def test_ipynb_diff_with_no_change() -> None:
357 result = runner.invoke(
360 str(DATA_DIR / "notebook_without_changes.ipynb"),
364 expected = "1 file would be left unchanged."
365 assert expected in result.output
368 def test_cache_isnt_written_if_no_jupyter_deps_single(
369 monkeypatch: MonkeyPatch, tmpdir: local
371 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
372 jupyter_dependencies_are_installed.cache_clear()
373 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
374 tmp_nb = tmpdir / "notebook.ipynb"
375 with open(nb) as src, open(tmp_nb, "w") as dst:
376 dst.write(src.read())
378 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
380 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
381 assert "No Python files are present to be formatted. Nothing to do" in result.output
382 jupyter_dependencies_are_installed.cache_clear()
384 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
386 result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
387 assert "reformatted" in result.output
390 def test_cache_isnt_written_if_no_jupyter_deps_dir(
391 monkeypatch: MonkeyPatch, tmpdir: local
393 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
394 jupyter_dependencies_are_installed.cache_clear()
395 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
396 tmp_nb = tmpdir / "notebook.ipynb"
397 with open(nb) as src, open(tmp_nb, "w") as dst:
398 dst.write(src.read())
400 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
402 result = runner.invoke(main, [str(tmpdir)])
403 assert "No Python files are present to be formatted. Nothing to do" in result.output
404 jupyter_dependencies_are_installed.cache_clear()
406 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
408 result = runner.invoke(main, [str(tmpdir)])
409 assert "reformatted" in result.output
412 def test_ipynb_flag(tmpdir: local) -> None:
413 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
414 tmp_nb = tmpdir / "notebook.a_file_extension_which_is_definitely_not_ipynb"
415 with open(nb) as src, open(tmp_nb, "w") as dst:
416 dst.write(src.read())
417 result = runner.invoke(
425 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
426 assert expected in result.output
429 def test_ipynb_and_pyi_flags() -> None:
430 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
431 result = runner.invoke(
440 assert isinstance(result.exception, SystemExit)
441 expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
442 assert result.output == expected
445 def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None:
446 src = "%%time\na = 'foo'"
447 monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo")
449 AssertionError, match="Black was not able to replace IPython magic"
451 format_cell(src, fast=True, mode=JUPYTER_MODE)