|
170 | 170 | "source": [ |
171 | 171 | "test_nb = read_nb('../tests/metadata.ipynb')\n", |
172 | 172 | "\n", |
173 | | - "assert set(['meta', 'jekyll', 'my_extra_key', 'my_removed_key']) <= set(test_nb.metadata.keys())\n", |
174 | | - "assert set(['meta', 'hide_input', 'my_extra_cell_key', 'my_removed_cell_key']) == set(test_nb.cells[1].metadata.keys())" |
| 173 | + "assert {'meta', 'jekyll', 'my_extra_key', 'my_removed_key'} <= test_nb.metadata.keys()\n", |
| 174 | + "assert {'meta', 'hide_input', 'my_extra_cell_key', 'my_removed_cell_key'} == test_nb.cells[1].metadata.keys()" |
175 | 175 | ] |
176 | 176 | }, |
177 | 177 | { |
|
189 | 189 | "source": [ |
190 | 190 | "clean_nb(test_nb)\n", |
191 | 191 | "\n", |
192 | | - "assert set(['jekyll', 'kernelspec']) == set(test_nb.metadata.keys())\n", |
193 | | - "assert set(['hide_input']) == set(test_nb.cells[1].metadata.keys())" |
| 192 | + "assert {'jekyll', 'kernelspec'} == test_nb.metadata.keys()\n", |
| 193 | + "assert {'hide_input'} == test_nb.cells[1].metadata.keys()" |
194 | 194 | ] |
195 | 195 | }, |
196 | 196 | { |
|
209 | 209 | "test_nb = read_nb('../tests/metadata.ipynb')\n", |
210 | 210 | "clean_nb(test_nb, allowed_metadata_keys={'my_extra_key'}, allowed_cell_metadata_keys={'my_extra_cell_key'})\n", |
211 | 211 | "\n", |
212 | | - "assert set(['jekyll', 'kernelspec', 'my_extra_key']) == set(test_nb.metadata.keys())\n", |
213 | | - "assert set(['hide_input', 'my_extra_cell_key']) == set(test_nb.cells[1].metadata.keys())" |
| 212 | + "assert {'jekyll', 'kernelspec', 'my_extra_key'} == test_nb.metadata.keys()\n", |
| 213 | + "assert {'hide_input', 'my_extra_cell_key'} == test_nb.cells[1].metadata.keys()" |
214 | 214 | ] |
215 | 215 | }, |
216 | 216 | { |
|
229 | 229 | "test_nb = read_nb('../tests/metadata.ipynb')\n", |
230 | 230 | "clean_nb(test_nb, clear_all=True)\n", |
231 | 231 | "\n", |
232 | | - "assert set(['jekyll', 'kernelspec']) == set(test_nb.metadata.keys())\n", |
| 232 | + "assert {'jekyll', 'kernelspec'} == test_nb.metadata.keys()\n", |
233 | 233 | "test_eq(test_nb.cells[1].metadata, {})" |
234 | 234 | ] |
235 | 235 | }, |
|
272 | 272 | " warn(e)" |
273 | 273 | ] |
274 | 274 | }, |
| 275 | + { |
| 276 | + "cell_type": "code", |
| 277 | + "execution_count": null, |
| 278 | + "metadata": {}, |
| 279 | + "outputs": [], |
| 280 | + "source": [ |
| 281 | + "#|export\n", |
| 282 | + "def _nbdev_clean(nb, **kwargs):\n", |
| 283 | + " allowed_metadata_keys = config_key(\"allowed_metadata_keys\", '', missing_ok=True, path=False).split()\n", |
| 284 | + " allowed_cell_metadata_keys = config_key(\"allowed_cell_metadata_keys\", '', missing_ok=True, path=False).split()\n", |
| 285 | + " return clean_nb(nb, allowed_metadata_keys=allowed_metadata_keys,\n", |
| 286 | + " allowed_cell_metadata_keys=allowed_cell_metadata_keys, **kwargs)" |
| 287 | + ] |
| 288 | + }, |
275 | 289 | { |
276 | 290 | "cell_type": "code", |
277 | 291 | "execution_count": null, |
|
288 | 302 | "):\n", |
289 | 303 | " \"Clean all notebooks in `fname` to avoid merge conflicts\"\n", |
290 | 304 | " # Git hooks will pass the notebooks in stdin\n", |
291 | | - " allowed_metadata_keys = config_key(\"allowed_metadata_keys\", default='', missing_ok=True, path=False).split()\n", |
292 | | - " allowed_cell_metadata_keys = config_key(\"allowed_cell_metadata_keys\", default='', missing_ok=True, path=False).split()\n", |
293 | | - " _clean = partial(clean_nb, clear_all=clear_all,\n", |
294 | | - " allowed_metadata_keys=allowed_metadata_keys,\n", |
295 | | - " allowed_cell_metadata_keys=allowed_cell_metadata_keys)\n", |
| 305 | + " _clean = partial(_nbdev_clean, clear_all=clear_all)\n", |
296 | 306 | " _write = partial(process_write, warn_msg='Failed to clean notebook', proc_nb=_clean)\n", |
297 | 307 | " if stdin: return _write(f_in=sys.stdin, f_out=sys.stdout)\n", |
298 | 308 | " \n", |
|
357 | 367 | " (nb_path/'.gitattributes').write_text(\"**/*.ipynb filter=clean-nbs\\n**/*.ipynb diff=ipynb\\n\")" |
358 | 368 | ] |
359 | 369 | }, |
| 370 | + { |
| 371 | + "cell_type": "code", |
| 372 | + "execution_count": null, |
| 373 | + "metadata": {}, |
| 374 | + "outputs": [], |
| 375 | + "source": [ |
| 376 | + "#|export\n", |
| 377 | + "def clean_jupyter(path, model, **kwargs):\n", |
| 378 | + " \"Clean Jupyter `model` pre save to `path`\"\n", |
| 379 | + " get_config.cache_clear() # Reset Jupyter's cache\n", |
| 380 | + " try: cfg = get_config(path=path)\n", |
| 381 | + " except FileNotFoundError: return\n", |
| 382 | + " in_nbdev_repo = 'nbs_path' in cfg\n", |
| 383 | + " jupyter_hooks = str2bool(cfg.get('jupyter_hooks', True))\n", |
| 384 | + " is_nb_v4 = (model['type'],model['content']['nbformat']) == ('notebook',4)\n", |
| 385 | + " if in_nbdev_repo and jupyter_hooks and is_nb_v4: _nbdev_clean(model['content'])" |
| 386 | + ] |
| 387 | + }, |
| 388 | + { |
| 389 | + "cell_type": "markdown", |
| 390 | + "metadata": {}, |
| 391 | + "source": [ |
| 392 | + "`clean_jupyter` implements Jupyter's [`ContentsManager.pre_save_hook`](https://jupyter-notebook.readthedocs.io/en/6.4.12/extending/savehooks.html). The easiest way to install it as a Jupyter Notebook or Lab pre-save hook is by running `nbdev_install_jupyter_hooks`." |
| 393 | + ] |
| 394 | + }, |
| 395 | + { |
| 396 | + "cell_type": "code", |
| 397 | + "execution_count": null, |
| 398 | + "metadata": {}, |
| 399 | + "outputs": [], |
| 400 | + "source": [ |
| 401 | + "#|export\n", |
| 402 | + "def _nested_setdefault(o, attr, default):\n", |
| 403 | + " \"Same as `setdefault`, but if `attr` includes a `.`, then looks inside nested objects\"\n", |
| 404 | + " attrs = attr.split('.')\n", |
| 405 | + " for a in attrs[:-1]: o = o.setdefault(a, type(o)())\n", |
| 406 | + " return o.setdefault(attrs[-1], default)" |
| 407 | + ] |
| 408 | + }, |
| 409 | + { |
| 410 | + "cell_type": "code", |
| 411 | + "execution_count": null, |
| 412 | + "metadata": {}, |
| 413 | + "outputs": [], |
| 414 | + "source": [ |
| 415 | + "#|hide\n", |
| 416 | + "o = {'e':'f'}\n", |
| 417 | + "test_eq(_nested_setdefault(o, 'a.b.c', 'd'), 'd')\n", |
| 418 | + "test_eq(o, {'a':{'b':{'c':'d'}},'e':'f'})" |
| 419 | + ] |
| 420 | + }, |
| 421 | + { |
| 422 | + "cell_type": "code", |
| 423 | + "execution_count": null, |
| 424 | + "metadata": {}, |
| 425 | + "outputs": [], |
| 426 | + "source": [ |
| 427 | + "#|hide\n", |
| 428 | + "o = {'a':'b'}\n", |
| 429 | + "test_eq(_nested_setdefault(o, 'a', 'c'), 'b')\n", |
| 430 | + "test_eq(o, {'a':'b'})" |
| 431 | + ] |
| 432 | + }, |
| 433 | + { |
| 434 | + "cell_type": "code", |
| 435 | + "execution_count": null, |
| 436 | + "metadata": {}, |
| 437 | + "outputs": [], |
| 438 | + "source": [ |
| 439 | + "#|hide\n", |
| 440 | + "o = {'a':{'b':'c'}}\n", |
| 441 | + "test_eq(_nested_setdefault(o, 'a.b', 'd'), 'c')\n", |
| 442 | + "test_eq(o,{'a':{'b':'c'}})" |
| 443 | + ] |
| 444 | + }, |
| 445 | + { |
| 446 | + "cell_type": "code", |
| 447 | + "execution_count": null, |
| 448 | + "metadata": {}, |
| 449 | + "outputs": [], |
| 450 | + "source": [ |
| 451 | + "#|export\n", |
| 452 | + "@call_parse\n", |
| 453 | + "def nbdev_install_jupyter_hooks():\n", |
| 454 | + " \"Install Jupyter hooks to clean notebooks on save\"\n", |
| 455 | + " cfg_path = Path.home()/'.jupyter'\n", |
| 456 | + " cfg_fns = [cfg_path/f'jupyter_{o}_config.json' for o in ('notebook','server')]\n", |
| 457 | + " attr,hook = 'ContentsManager.pre_save_hook','nbdev.clean.clean_jupyter'\n", |
| 458 | + " for fn in cfg_fns:\n", |
| 459 | + " cfg = dict2obj(fn.read_json() if fn.exists() else {})\n", |
| 460 | + " val = nested_attr(cfg, attr)\n", |
| 461 | + " if val is None:\n", |
| 462 | + " _nested_setdefault(cfg, attr, hook)\n", |
| 463 | + " fn.write_text(dumps(obj2dict(cfg), indent=2))\n", |
| 464 | + " elif val != hook:\n", |
| 465 | + " sys.stderr.write(f\"Can't install hook to '{p}' since it already contains `{attr} = '{val}'`. \"\n", |
| 466 | + " f\"Manually update to `{attr} = '{hook}'` for this functionality.\")" |
| 467 | + ] |
| 468 | + }, |
| 469 | + { |
| 470 | + "cell_type": "markdown", |
| 471 | + "metadata": {}, |
| 472 | + "source": [ |
| 473 | + "`nbdev`'s Jupyter hooks only run on notebooks in an `nbdev` repo. Hooks can also be disabled at any time by setting `jupyter_hooks = False` in `settings.ini`." |
| 474 | + ] |
| 475 | + }, |
360 | 476 | { |
361 | 477 | "cell_type": "markdown", |
362 | 478 | "metadata": {}, |
|
0 commit comments