|
1 | 1 | import os
|
2 | 2 | import tempfile
|
| 3 | +from pathlib import Path |
3 | 4 | from typing import Any
|
| 5 | +from warnings import warn |
4 | 6 |
|
5 | 7 | from IPython.core.interactiveshell import InteractiveShell
|
6 | 8 | from IPython.core.magic import line_magic
|
7 | 9 | from IPython.core.magic import Magics
|
8 | 10 | from IPython.core.magic import magics_class
|
9 | 11 | from IPython.core.magic import needs_local_scope
|
10 | 12 | from IPython.core.magic import no_var_expand
|
| 13 | +from IPython.utils.contexts import preserve_keys |
| 14 | +from IPython.utils.syspathcontext import prepended_to_syspath |
11 | 15 | from jinja2 import Template
|
12 | 16 |
|
13 | 17 |
|
@@ -53,4 +57,81 @@ def run_personal(self, line: str, local_ns: Any = None) -> Any:
|
53 | 57 | # Execute the SQL command
|
54 | 58 | self.shell.run_line_magic('sql', sql_command)
|
55 | 59 | # Run the downloaded file
|
56 |
| - self.shell.run_line_magic('run', f'"{temp_file_path}"') |
| 60 | + with preserve_keys(self.shell.user_ns, '__file__'): |
| 61 | + self.shell.user_ns['__file__'] = temp_file_path |
| 62 | + self.safe_execfile_ipy(temp_file_path, raise_exceptions=True) |
| 63 | + |
| 64 | + def safe_execfile_ipy( |
| 65 | + self, |
| 66 | + fname: str, |
| 67 | + shell_futures: bool = False, |
| 68 | + raise_exceptions: bool = False, |
| 69 | + ) -> None: |
| 70 | + """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax. |
| 71 | +
|
| 72 | + Parameters |
| 73 | + ---------- |
| 74 | + fname : str |
| 75 | + The name of the file to execute. The filename must have a |
| 76 | + .ipy or .ipynb extension. |
| 77 | + shell_futures : bool (False) |
| 78 | + If True, the code will share future statements with the interactive |
| 79 | + shell. It will both be affected by previous __future__ imports, and |
| 80 | + any __future__ imports in the code will affect the shell. If False, |
| 81 | + __future__ imports are not shared in either direction. |
| 82 | + raise_exceptions : bool (False) |
| 83 | + If True raise exceptions everywhere. Meant for testing. |
| 84 | + """ |
| 85 | + fpath = Path(fname).expanduser().resolve() |
| 86 | + |
| 87 | + # Make sure we can open the file |
| 88 | + try: |
| 89 | + with fpath.open('rb'): |
| 90 | + pass |
| 91 | + except Exception: |
| 92 | + warn('Could not open file <%s> for safe execution.' % fpath) |
| 93 | + return |
| 94 | + |
| 95 | + # Find things also in current directory. This is needed to mimic the |
| 96 | + # behavior of running a script from the system command line, where |
| 97 | + # Python inserts the script's directory into sys.path |
| 98 | + dname = str(fpath.parent) |
| 99 | + |
| 100 | + def get_cells() -> Any: |
| 101 | + """generator for sequence of code blocks to run""" |
| 102 | + if fpath.suffix == '.ipynb': |
| 103 | + from nbformat import read |
| 104 | + nb = read(fpath, as_version=4) |
| 105 | + if not nb.cells: |
| 106 | + return |
| 107 | + for cell in nb.cells: |
| 108 | + if cell.cell_type == 'code': |
| 109 | + if not cell.source.strip(): |
| 110 | + continue |
| 111 | + output_redirect = getattr( |
| 112 | + cell, 'metadata', {}, |
| 113 | + ).get('output_variable', '') or '' |
| 114 | + if output_redirect: |
| 115 | + output_redirect = f' {output_redirect} <<' |
| 116 | + if getattr(cell, 'metadata', {}).get('language', '') == 'sql': |
| 117 | + yield f'%%sql{output_redirect}\n{cell.source}' |
| 118 | + else: |
| 119 | + yield cell.source |
| 120 | + else: |
| 121 | + yield fpath.read_text(encoding='utf-8') |
| 122 | + |
| 123 | + with prepended_to_syspath(dname): |
| 124 | + try: |
| 125 | + for cell in get_cells(): |
| 126 | + result = self.shell.run_cell( |
| 127 | + cell, silent=True, shell_futures=shell_futures, |
| 128 | + ) |
| 129 | + if raise_exceptions: |
| 130 | + result.raise_error() |
| 131 | + elif not result.success: |
| 132 | + break |
| 133 | + except Exception: |
| 134 | + if raise_exceptions: |
| 135 | + raise |
| 136 | + self.shell.showtraceback() |
| 137 | + warn('Unknown failure executing file: <%s>' % fpath) |
0 commit comments