|
84 | 84 | "\n",
|
85 | 85 | "from IPython.core.magic import register_line_magic, register_cell_magic, needs_local_scope\n",
|
86 | 86 | "from IPython import get_ipython\n",
|
87 |
| - "import re, sys" |
| 87 | + "import re, sys, os" |
88 | 88 | ]
|
89 | 89 | },
|
90 | 90 | {
|
|
178 | 178 | },
|
179 | 179 | {
|
180 | 180 | "cell_type": "markdown",
|
181 |
| - "id": "9c7ecc7a-0058-4d2f-be99-0d50a6fe0519", |
| 181 | + "id": "ca63138c-13ff-445e-8a55-7176def3366f", |
182 | 182 | "metadata": {},
|
183 | 183 | "source": [
|
184 |
| - "## nb_export\n", |
185 |
| - "\n", |
186 |
| - "Next we can use the nb_export function to compile the exported cells into a python file. The second parameter tells nbdev which package to export the module to. Let's go ahead and export this notebook to the `dashboard` package. \n", |
| 184 | + "### %answer line magic\n", |
187 | 185 | "\n",
|
188 |
| - "> **IMPORTANT**: do not forget to save before exporting the notebook, or your most recent changes will not make it into the py file. It is also a good habit to \"Restart Kernel and Clear All Outputs\" and then save your notebook at the same time as you export. That way, when you get around to committing your code, your git diff isn't filled up with a bunch of serial numbers and other unintelligible things." |
| 186 | + "The code below allows us to import code from the nbdev-generated python files like the one we loaded above. You can think of this function working like the `%load` line magic, which pulls in code from an external file. But instead of loading the entire file, this function loads only the python code that came from an exported notebook cell. " |
189 | 187 | ]
|
190 | 188 | },
|
191 | 189 | {
|
192 | 190 | "cell_type": "code",
|
193 | 191 | "execution_count": null,
|
194 |
| - "id": "0b8b5029-0636-4dda-ae11-d5641450ec35", |
| 192 | + "id": "bf826708-05c7-48a6-b8ce-8423fe897faf", |
195 | 193 | "metadata": {},
|
196 | 194 | "outputs": [],
|
197 | 195 | "source": [
|
198 |
| - "from nbdev.export import nb_export\n", |
| 196 | + "#| export\n", |
| 197 | + "@register_line_magic('answer')\n", |
| 198 | + "def answer(inputs):\n", |
| 199 | + " '''\n", |
| 200 | + " This is a cell magic called answer that allows tutorial goers to import the correct answer from the key. \n", |
| 201 | + " '''\n", |
| 202 | + " words = []\n", |
| 203 | + " for word in inputs.split(' '):\n", |
| 204 | + " if not word.startswith('#') and len(word) != 0:\n", |
| 205 | + " words.append(word)\n", |
| 206 | + " else:\n", |
| 207 | + " break\n", |
| 208 | + " \n", |
| 209 | + " flag = False\n", |
| 210 | + " if len(words) == 2:\n", |
| 211 | + " if words[1] == '-e':\n", |
| 212 | + " flag = True\n", |
| 213 | + " else:\n", |
| 214 | + " filepath = words[0]\n", |
| 215 | + " cell_number = int(words[1])\n", |
199 | 216 | "\n",
|
200 |
| - "nb_export('03_nbdev.ipynb', 'dashboard')\n", |
201 |
| - "nb_export('03_nbdev.ipynb', 'key/dashboard')" |
202 |
| - ] |
203 |
| - }, |
204 |
| - { |
205 |
| - "cell_type": "markdown", |
206 |
| - "id": "dcd23a2c-ec98-4ab5-a362-1ff9e1e5f37d", |
207 |
| - "metadata": {}, |
208 |
| - "source": [ |
209 |
| - "Usually, we will put `nb_export` at the end of our file for convenience, but we will continue on from here." |
| 217 | + " with open(filepath, 'r') as file:\n", |
| 218 | + " lines = file.readlines()\n", |
| 219 | + "\n", |
| 220 | + " pattern = r'# %%\\s+(.+)\\s+(\\d+)'\n", |
| 221 | + " start_line = None\n", |
| 222 | + " end_line = None\n", |
| 223 | + "\n", |
| 224 | + " for i, line in enumerate(lines):\n", |
| 225 | + " if re.match(pattern, line):\n", |
| 226 | + " match = re.search(pattern, line)\n", |
| 227 | + " if match and int(match.group(2)) == cell_number:\n", |
| 228 | + " start_line = i + 1\n", |
| 229 | + " break\n", |
| 230 | + " if start_line is not None:\n", |
| 231 | + " for i in range(start_line, len(lines)):\n", |
| 232 | + " if re.match(pattern, lines[i]):\n", |
| 233 | + " end_line = i\n", |
| 234 | + " break\n", |
| 235 | + " else:\n", |
| 236 | + " end_line = len(lines)\n", |
| 237 | + "\n", |
| 238 | + " if start_line is not None and end_line is not None:\n", |
| 239 | + " code_chunk = f\"#| export\\n# %answer {inputs}\\n\\n\" + ''.join(lines[start_line:end_line])\n", |
| 240 | + " code_chunk = code_chunk.rstrip(\"\\n\")\n", |
| 241 | + " get_ipython().set_next_input(code_chunk, replace=True)\n", |
| 242 | + " else:\n", |
| 243 | + " raise Exception(f\"Cell number {cell_number} not found in the Python file.\")\n", |
| 244 | + " \n", |
| 245 | + " if len(words) == 1 or words[1] == '-e':\n", |
| 246 | + " filepath = words[0]\n", |
| 247 | + " with open(filepath, 'r') as file:\n", |
| 248 | + " lines = file.readlines()\n", |
| 249 | + " code_chunk = ''.join(lines[:])\n", |
| 250 | + " if flag:\n", |
| 251 | + " code_chunk = f\"# %%export {filepath}\\n\\n\" + code_chunk\n", |
| 252 | + " else: \n", |
| 253 | + " code_chunk = f\"# %answer {filepath}\\n\\n\" + code_chunk\n", |
| 254 | + " get_ipython().set_next_input(code_chunk, replace=True)\n", |
| 255 | + " \n", |
| 256 | + " with open(filepath, 'r') as file:\n", |
| 257 | + " lines = file.readlines()" |
210 | 258 | ]
|
211 | 259 | },
|
212 | 260 | {
|
213 | 261 | "cell_type": "markdown",
|
214 |
| - "id": "72d3d366-c995-4e87-9db0-5eb02ba04142", |
| 262 | + "id": "8550a7c2-5176-4a9e-b66d-db1ccd44fdf3", |
215 | 263 | "metadata": {},
|
216 | 264 | "source": [
|
217 |
| - "Given this information, what file do you think the code will be exported to? Remember that our default export was set to `__init__`." |
| 265 | + "If at any point in this tutorial you have trouble coming up with an answer on your own, you can always import it from the answer key.\n", |
| 266 | + "\n", |
| 267 | + "### Pull from exported module\n", |
| 268 | + "\n", |
| 269 | + "This will be the case when we are pulling in an answer to a cell that starts with `#| export`. The path to the answer key files is the same as the default, but is preceeded by the `key` directory. For example, the next line will load the code from the 6th cell of this notebook, which is the import statements. " |
218 | 270 | ]
|
219 | 271 | },
|
220 | 272 | {
|
221 | 273 | "cell_type": "code",
|
222 | 274 | "execution_count": null,
|
223 |
| - "id": "3a56bb17-3345-402e-9f99-4d295048ed85", |
| 275 | + "id": "d2cb3849-b5f0-4313-928e-1cf3bcc425d2", |
224 | 276 | "metadata": {},
|
225 | 277 | "outputs": [],
|
226 | 278 | "source": [
|
227 |
| - "%load dashboard/__init__.py" |
| 279 | + "# %answer key/dashboard/__init__.py 6" |
228 | 280 | ]
|
229 | 281 | },
|
230 | 282 | {
|
231 | 283 | "cell_type": "markdown",
|
232 |
| - "id": "4ef6b07d-1023-48ec-bdb6-879d1c7d216d", |
| 284 | + "id": "7c9ff319-d009-4d66-8eea-e663b3f41d62", |
233 | 285 | "metadata": {},
|
234 | 286 | "source": [
|
235 |
| - "There are a few thing to notice here. One is that we get a warning about this file being autogenerated. That is, if we make changes to `dashboard/__init__.py`, those changes will be overwritten the next time we run `nb_export`.\n", |
| 287 | + "### Pull from file\n", |
236 | 288 | "\n",
|
237 |
| - "The second thing to notice is that there is a handy comment to tell us where the code came from. `# %% ../_nbdev.ipynb 9` tells us that the code came from the 9th code cell. We will use this information to write our next magic function." |
| 289 | + "Sometimes we will want to check an answer from a cell that isn't a module exported by nbdev. In this case, we will provide the file path only.\n", |
| 290 | + "\n", |
| 291 | + "**In both cases, these paths will be provided for you in comments.**" |
238 | 292 | ]
|
239 | 293 | },
|
240 | 294 | {
|
241 | 295 | "cell_type": "markdown",
|
242 |
| - "id": "ca63138c-13ff-445e-8a55-7176def3366f", |
| 296 | + "id": "9c7ecc7a-0058-4d2f-be99-0d50a6fe0519", |
243 | 297 | "metadata": {},
|
244 | 298 | "source": [
|
245 |
| - "### %answer line magic\n", |
| 299 | + "## nb_export\n", |
246 | 300 | "\n",
|
247 |
| - "The code below allows us to import code from the nbdev-generated python files like the one we loaded above. You can think of this function working like the `%load` line magic, which pulls in code from an external file. But instead of loading the entire file, this function loads only the python code that came from an exported notebook cell. " |
| 301 | + "Next we can use the nb_export function to compile the exported cells into a python file. The second parameter tells nbdev which package to export the module to. Let's go ahead and export this notebook to the `dashboard` package. \n", |
| 302 | + "\n", |
| 303 | + "> **IMPORTANT**: do not forget to save before exporting the notebook, or your most recent changes will not make it into the py file. It is also a good habit to \"Restart Kernel and Clear All Outputs\" and then save your notebook at the same time as you export. That way, when you get around to committing your code, your git diff isn't filled up with a bunch of serial numbers and other unintelligible things." |
248 | 304 | ]
|
249 | 305 | },
|
250 | 306 | {
|
251 | 307 | "cell_type": "code",
|
252 | 308 | "execution_count": null,
|
253 |
| - "id": "fd6d6913-b174-4214-86fe-6985939ed041", |
| 309 | + "id": "0b8b5029-0636-4dda-ae11-d5641450ec35", |
254 | 310 | "metadata": {},
|
255 | 311 | "outputs": [],
|
256 | 312 | "source": [
|
257 |
| - "#| export \n", |
| 313 | + "from nbdev.export import nb_export\n", |
258 | 314 | "\n",
|
259 |
| - "@register_line_magic('answer')\n", |
260 |
| - "def answer(inputs):\n", |
261 |
| - " '''\n", |
262 |
| - " This is a cell magic called answer that allows tutorial goers to import the correct answer from the key. \n", |
263 |
| - " '''\n", |
264 |
| - " words = []\n", |
265 |
| - " for word in inputs.split(' '):\n", |
266 |
| - " if not word.startswith('#'):\n", |
267 |
| - " words.append(word)\n", |
268 |
| - " else:\n", |
269 |
| - " break\n", |
270 |
| - " \n", |
271 |
| - " assert len(words) == 2, \"%answer takes a filepath and a cell number\"\n", |
272 |
| - " filepath = words[0]\n", |
273 |
| - " cell_number = int(words[1])\n", |
274 |
| - " \n", |
275 |
| - " with open(filepath, 'r') as file:\n", |
276 |
| - " lines = file.readlines()\n", |
277 |
| - "\n", |
278 |
| - " pattern = r'# %% .+ \\d+'\n", |
279 |
| - " start_line = None\n", |
280 |
| - " end_line = None\n", |
281 |
| - "\n", |
282 |
| - " for i, line in enumerate(lines):\n", |
283 |
| - " if re.match(pattern, line):\n", |
284 |
| - " match = re.search(r'\\d+', line)\n", |
285 |
| - " if match and int(match.group()) == cell_number:\n", |
286 |
| - " start_line = i + 1\n", |
287 |
| - " break\n", |
288 |
| - " if start_line is not None:\n", |
289 |
| - " for i in range(start_line, len(lines)):\n", |
290 |
| - " if re.match(pattern, lines[i]):\n", |
291 |
| - " end_line = i\n", |
292 |
| - " break\n", |
293 |
| - " else:\n", |
294 |
| - " end_line = len(lines)\n", |
295 |
| - " \n", |
296 |
| - " if start_line is not None and end_line is not None:\n", |
297 |
| - " code_chunk = f\"# %answer {inputs}\\n\" + ''.join(lines[start_line:end_line])\n", |
298 |
| - " code_chunk = code_chunk.rstrip(\"\\n\")\n", |
299 |
| - " get_ipython().set_next_input(code_chunk, replace=True)\n", |
300 |
| - " else:\n", |
301 |
| - " print(f\"Cell number {cell_number} not found in the Python file.\")" |
| 315 | + "nb_export('03_nbdev.ipynb', 'dashboard')" |
302 | 316 | ]
|
303 | 317 | },
|
304 | 318 | {
|
305 | 319 | "cell_type": "markdown",
|
306 |
| - "id": "8550a7c2-5176-4a9e-b66d-db1ccd44fdf3", |
307 |
| - "metadata": {}, |
308 |
| - "source": [ |
309 |
| - "If at any point in this tutorial you have trouble coming up with an answer on your own, you can always import it from the answer key. The path to the answer key files is the same as the default, but is preceeded by the `key` directory. These paths will be provided for you in comments.\n", |
310 |
| - "\n", |
311 |
| - "What do you think the directory is for the answer key to this notebook? Try to load the code from the 9th cell of this notebook." |
312 |
| - ] |
313 |
| - }, |
314 |
| - { |
315 |
| - "cell_type": "code", |
316 |
| - "execution_count": null, |
317 |
| - "id": "bfcce99c-1a35-4e32-81e0-38ac16ae14c2", |
| 320 | + "id": "dcd23a2c-ec98-4ab5-a362-1ff9e1e5f37d", |
318 | 321 | "metadata": {},
|
319 |
| - "outputs": [], |
320 | 322 | "source": [
|
321 |
| - "%answer key/dashboard/__init__.py 9 # %answer <path>/<to>/<file>.py 9" |
| 323 | + "Usually, we will put `nb_export` at the end of our file for convenience, but we will continue on from here." |
322 | 324 | ]
|
323 | 325 | },
|
324 | 326 | {
|
|
336 | 338 | "metadata": {},
|
337 | 339 | "outputs": [],
|
338 | 340 | "source": [
|
339 |
| - "import dashboard # import ..." |
| 341 | + "# %answer 03/01.py\n", |
| 342 | + "\n", |
| 343 | + "# import ..." |
340 | 344 | ]
|
341 | 345 | },
|
342 | 346 | {
|
|
346 | 350 | "source": [
|
347 | 351 | "So for most modules, the import statement would look like `dashboard.__init__` but the `__init__` file is special, and will be imported any time we make an import from `dashboard` package.\n",
|
348 | 352 | "\n",
|
349 |
| - "Great job! Now we know the basic idea of what nbdev does." |
| 353 | + "Great job! Now we know the basic idea of what nbdev does. Go ahead and use the `%load` line magic inspect the exported file below." |
350 | 354 | ]
|
351 | 355 | },
|
352 | 356 | {
|
|
356 | 360 | "metadata": {},
|
357 | 361 | "outputs": [],
|
358 | 362 | "source": [
|
359 |
| - "%load dashboard/__init__.py # %load pkg/module.py" |
| 363 | + "# %answer key/03/02.py\n", |
| 364 | + "\n", |
| 365 | + "# %load pkg/module.py" |
| 366 | + ] |
| 367 | + }, |
| 368 | + { |
| 369 | + "cell_type": "markdown", |
| 370 | + "id": "4ef6b07d-1023-48ec-bdb6-879d1c7d216d", |
| 371 | + "metadata": {}, |
| 372 | + "source": [ |
| 373 | + "There are a few thing to notice here. One is that we get a warning about this file being autogenerated. That is, if we make changes to `dashboard/__init__.py`, those changes will be overwritten the next time we run `nb_export`.\n", |
| 374 | + "\n", |
| 375 | + "The second thing to notice is that there is a handy comment to tell us where the code came from. `# %% ../_nbdev.ipynb 9` tells us that the code came from the 9th code cell. We will use this information to write our next magic function." |
360 | 376 | ]
|
361 | 377 | },
|
362 | 378 | {
|
|
369 | 385 | "Of course, nbdev has a lot of other nice features, but the notebook export is the core of literate programming. You can do your work incrementally, leave in a lot of markdown, and read your code like a book. At the same time, you can keep the code in clean, importable python files."
|
370 | 386 | ]
|
371 | 387 | },
|
| 388 | + { |
| 389 | + "cell_type": "markdown", |
| 390 | + "id": "4fa634ee-53f9-41d1-8ad4-8d55f5e154be", |
| 391 | + "metadata": {}, |
| 392 | + "source": [ |
| 393 | + "### The End\n", |
| 394 | + "\n", |
| 395 | + "We will not go over the code below. Proceed to the next notebook.\n", |
| 396 | + "___" |
| 397 | + ] |
| 398 | + }, |
372 | 399 | {
|
373 | 400 | "cell_type": "code",
|
374 | 401 | "execution_count": null,
|
375 |
| - "id": "aea02e66-ce09-44f5-ac85-b5539ae1a08d", |
| 402 | + "id": "97fb40d0-242e-4aea-aa50-d054d38f8b55", |
376 | 403 | "metadata": {},
|
377 | 404 | "outputs": [],
|
378 |
| - "source": [] |
| 405 | + "source": [ |
| 406 | + "#| export\n", |
| 407 | + "@register_cell_magic('export')\n", |
| 408 | + "def export(line, cell=None):\n", |
| 409 | + " line_args = line.split()\n", |
| 410 | + " export_filepath = None\n", |
| 411 | + "\n", |
| 412 | + " if len(line_args):\n", |
| 413 | + " export_filepath = line_args[0]\n", |
| 414 | + " directory = os.path.dirname(export_filepath)\n", |
| 415 | + " os.makedirs(directory, exist_ok=True)\n", |
| 416 | + " with open(export_filepath, 'w') as file:\n", |
| 417 | + " file.write(cell)\n", |
| 418 | + " print(f\"exported to {export_filepath}\")\n", |
| 419 | + " \n", |
| 420 | + " processed_lines = []\n", |
| 421 | + " for line in cell.split('\\n'):\n", |
| 422 | + " comment_match = re.search(r'#', line)\n", |
| 423 | + " if comment_match:\n", |
| 424 | + " line = line[comment_match.start():]\n", |
| 425 | + " processed_lines.append(line)\n", |
| 426 | + "\n", |
| 427 | + " processed_cell = '\\n'.join(processed_lines)\n", |
| 428 | + " if len(line_args):\n", |
| 429 | + " processed_cell = '# %answer ' + line_args[0] + '\\n\\n' + processed_cell\n", |
| 430 | + " get_ipython().set_next_input(processed_cell, replace=True)" |
| 431 | + ] |
379 | 432 | },
|
380 | 433 | {
|
381 | 434 | "cell_type": "code",
|
382 | 435 | "execution_count": null,
|
383 |
| - "id": "1c88008a-4058-4128-b3ca-0c2a1925da0d", |
| 436 | + "id": "f38ff439-b7c6-4f66-bcea-ca87a079d7cd", |
384 | 437 | "metadata": {},
|
385 | 438 | "outputs": [],
|
386 | 439 | "source": []
|
|
0 commit comments