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.
4 from click.testing import CliRunner
5 from black.handle_ipynb_magics import jupyter_dependencies_are_installed
14 from black import Mode
15 from _pytest.monkeypatch import MonkeyPatch
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",
109 "%%writefile e.txt\n meh\n meh",
112 def test_non_python_magics(src: str) -> None:
113 with pytest.raises(NothingChanged):
114 format_cell(src, fast=True, mode=JUPYTER_MODE)
117 def test_set_input() -> None:
119 with pytest.raises(NothingChanged):
120 format_cell(src, fast=True, mode=JUPYTER_MODE)
123 def test_input_already_contains_transformed_magic() -> None:
124 src = '%time foo()\nget_ipython().run_cell_magic("time", "", "foo()\\n")'
125 with pytest.raises(NothingChanged):
126 format_cell(src, fast=True, mode=JUPYTER_MODE)
129 def test_magic_noop() -> None:
131 with pytest.raises(NothingChanged):
132 format_cell(src, fast=True, mode=JUPYTER_MODE)
135 def test_cell_magic_with_magic() -> None:
136 src = "%%timeit -n1\nls =!ls"
137 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
138 expected = "%%timeit -n1\nls = !ls"
139 assert result == expected
142 def test_cell_magic_nested() -> None:
143 src = "%%time\n%%time\n2+2"
144 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
145 expected = "%%time\n%%time\n2 + 2"
146 assert result == expected
149 def test_cell_magic_with_magic_noop() -> None:
150 src = "%%t -n1\nls = !ls"
151 with pytest.raises(NothingChanged):
152 format_cell(src, fast=True, mode=JUPYTER_MODE)
155 def test_automagic() -> None:
156 src = "pip install black"
157 with pytest.raises(NothingChanged):
158 format_cell(src, fast=True, mode=JUPYTER_MODE)
161 def test_multiline_magic() -> None:
162 src = "%time 1 + \\\n2"
163 with pytest.raises(NothingChanged):
164 format_cell(src, fast=True, mode=JUPYTER_MODE)
167 def test_multiline_no_magic() -> None:
169 result = format_cell(src, fast=True, mode=JUPYTER_MODE)
171 assert result == expected
174 def test_cell_magic_with_invalid_body() -> None:
175 src = "%%time\nif True"
176 with pytest.raises(NothingChanged):
177 format_cell(src, fast=True, mode=JUPYTER_MODE)
180 def test_empty_cell() -> None:
182 with pytest.raises(NothingChanged):
183 format_cell(src, fast=True, mode=JUPYTER_MODE)
186 def test_entire_notebook_empty_metadata() -> None:
187 with open(DATA_DIR / "notebook_empty_metadata.ipynb", "rb") as fd:
188 content_bytes = fd.read()
189 content = content_bytes.decode()
190 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
195 ' "cell_type": "code",\n'
196 ' "execution_count": null,\n'
204 ' "print(\\"foo\\")"\n'
208 ' "cell_type": "code",\n'
209 ' "execution_count": null,\n'
217 ' "nbformat_minor": 4\n'
220 assert result == expected
223 def test_entire_notebook_trailing_newline() -> None:
224 with open(DATA_DIR / "notebook_trailing_newline.ipynb", "rb") as fd:
225 content_bytes = fd.read()
226 content = content_bytes.decode()
227 result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
232 ' "cell_type": "code",\n'
233 ' "execution_count": null,\n'
241 ' "print(\\"foo\\")"\n'
245 ' "cell_type": "code",\n'
246 ' "execution_count": null,\n'
253 ' "interpreter": {\n'
254 ' "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n' # noqa:B950
257 ' "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
258 ' "name": "python3"\n'
260 ' "language_info": {\n'
261 ' "name": "python",\n'
266 ' "nbformat_minor": 4\n'
269 assert result == expected
272 def test_entire_notebook_no_trailing_newline() -> None:
273 with open(DATA_DIR / "notebook_no_trailing_newline.ipynb", "rb") as fd:
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:
322 with open(DATA_DIR / "notebook_without_changes.ipynb", "rb") as fd:
323 content_bytes = fd.read()
324 content = content_bytes.decode()
325 with pytest.raises(NothingChanged):
326 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
329 def test_non_python_notebook() -> None:
330 with open(DATA_DIR / "non_python_notebook.ipynb", "rb") as fd:
331 content_bytes = fd.read()
332 content = content_bytes.decode()
333 with pytest.raises(NothingChanged):
334 format_file_contents(content, fast=True, mode=JUPYTER_MODE)
337 def test_empty_string() -> None:
338 with pytest.raises(NothingChanged):
339 format_file_contents("", fast=True, mode=JUPYTER_MODE)
342 def test_unparseable_notebook() -> None:
343 path = DATA_DIR / "notebook_which_cant_be_parsed.ipynb"
344 msg = rf"File '{re.escape(str(path))}' cannot be parsed as valid Jupyter notebook\."
345 with pytest.raises(ValueError, match=msg):
346 format_file_in_place(path, fast=True, mode=JUPYTER_MODE)
349 def test_ipynb_diff_with_change() -> None:
350 result = runner.invoke(
353 str(DATA_DIR / "notebook_trailing_newline.ipynb"),
357 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
358 assert expected in result.output
361 def test_ipynb_diff_with_no_change() -> None:
362 result = runner.invoke(
365 str(DATA_DIR / "notebook_without_changes.ipynb"),
369 expected = "1 file would be left unchanged."
370 assert expected in result.output
373 def test_cache_isnt_written_if_no_jupyter_deps_single(
374 monkeypatch: MonkeyPatch, tmp_path: pathlib.Path
376 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
377 jupyter_dependencies_are_installed.cache_clear()
378 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
379 tmp_nb = tmp_path / "notebook.ipynb"
380 with open(nb) as src, open(tmp_nb, "w") as dst:
381 dst.write(src.read())
383 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
385 result = runner.invoke(main, [str(tmp_path / "notebook.ipynb")])
386 assert "No Python files are present to be formatted. Nothing to do" in result.output
387 jupyter_dependencies_are_installed.cache_clear()
389 "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
391 result = runner.invoke(main, [str(tmp_path / "notebook.ipynb")])
392 assert "reformatted" in result.output
395 def test_cache_isnt_written_if_no_jupyter_deps_dir(
396 monkeypatch: MonkeyPatch, tmp_path: pathlib.Path
398 # Check that the cache isn't written to if Jupyter dependencies aren't installed.
399 jupyter_dependencies_are_installed.cache_clear()
400 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
401 tmp_nb = tmp_path / "notebook.ipynb"
402 with open(nb) as src, open(tmp_nb, "w") as dst:
403 dst.write(src.read())
405 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
407 result = runner.invoke(main, [str(tmp_path)])
408 assert "No Python files are present to be formatted. Nothing to do" in result.output
409 jupyter_dependencies_are_installed.cache_clear()
411 "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
413 result = runner.invoke(main, [str(tmp_path)])
414 assert "reformatted" in result.output
417 def test_ipynb_flag(tmp_path: pathlib.Path) -> None:
418 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
419 tmp_nb = tmp_path / "notebook.a_file_extension_which_is_definitely_not_ipynb"
420 with open(nb) as src, open(tmp_nb, "w") as dst:
421 dst.write(src.read())
422 result = runner.invoke(
430 expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
431 assert expected in result.output
434 def test_ipynb_and_pyi_flags() -> None:
435 nb = DATA_DIR / "notebook_trailing_newline.ipynb"
436 result = runner.invoke(
445 assert isinstance(result.exception, SystemExit)
446 expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
447 assert result.output == expected
450 def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None:
451 src = "%%time\na = 'foo'"
452 monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo")
454 AssertionError, match="Black was not able to replace IPython magic"
456 format_cell(src, fast=True, mode=JUPYTER_MODE)