Show More
@@ -500,7 +500,6 b' class InteractiveShell(SingletonConfigurable):' | |||
|
500 | 500 | def __init__(self, ipython_dir=None, profile_dir=None, |
|
501 | 501 | user_module=None, user_ns=None, |
|
502 | 502 | custom_exceptions=((), None), **kwargs): |
|
503 | ||
|
504 | 503 | # This is where traits with a config_key argument are updated |
|
505 | 504 | # from the values on config. |
|
506 | 505 | super(InteractiveShell, self).__init__(**kwargs) |
@@ -1643,7 +1642,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
1643 | 1642 | formatter, |
|
1644 | 1643 | info, |
|
1645 | 1644 | enable_html_pager=self.enable_html_pager, |
|
1646 | **kw | |
|
1645 | **kw, | |
|
1647 | 1646 | ) |
|
1648 | 1647 | else: |
|
1649 | 1648 | pmethod(info.obj, oname) |
@@ -2173,7 +2172,34 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2173 | 2172 | func, magic_kind=magic_kind, magic_name=magic_name |
|
2174 | 2173 | ) |
|
2175 | 2174 | |
|
2176 | def run_line_magic(self, magic_name, line, _stack_depth=1): | |
|
2175 | def _find_with_lazy_load(self, /, type_, magic_name: str): | |
|
2176 | """ | |
|
2177 | Try to find a magic potentially lazy-loading it. | |
|
2178 | ||
|
2179 | Parameters | |
|
2180 | ---------- | |
|
2181 | ||
|
2182 | type_: "line"|"cell" | |
|
2183 | the type of magics we are trying to find/lazy load. | |
|
2184 | magic_name: str | |
|
2185 | The name of the magic we are trying to find/lazy load | |
|
2186 | ||
|
2187 | ||
|
2188 | Note that this may have any side effects | |
|
2189 | """ | |
|
2190 | finder = {"line": self.find_line_magic, "cell": self.find_cell_magic}[type_] | |
|
2191 | fn = finder(magic_name) | |
|
2192 | if fn is not None: | |
|
2193 | return fn | |
|
2194 | lazy = self.magics_manager.lazy_magics.get(magic_name) | |
|
2195 | if lazy is None: | |
|
2196 | return None | |
|
2197 | ||
|
2198 | self.run_line_magic("load_ext", lazy) | |
|
2199 | res = finder(magic_name) | |
|
2200 | return res | |
|
2201 | ||
|
2202 | def run_line_magic(self, magic_name: str, line, _stack_depth=1): | |
|
2177 | 2203 | """Execute the given line magic. |
|
2178 | 2204 | |
|
2179 | 2205 | Parameters |
@@ -2186,6 +2212,11 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2186 | 2212 | If run_line_magic() is called from magic() then _stack_depth=2. |
|
2187 | 2213 | This is added to ensure backward compatibility for use of 'get_ipython().magic()' |
|
2188 | 2214 | """ |
|
2215 | fn = self._find_with_lazy_load("line", magic_name) | |
|
2216 | if fn is None: | |
|
2217 | lazy = self.magics_manager.lazy_magics.get(magic_name) | |
|
2218 | if lazy: | |
|
2219 | self.run_line_magic("load_ext", lazy) | |
|
2189 | 2220 | fn = self.find_line_magic(magic_name) |
|
2190 | 2221 | if fn is None: |
|
2191 | 2222 | cm = self.find_cell_magic(magic_name) |
@@ -2237,7 +2268,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2237 | 2268 | cell : str |
|
2238 | 2269 | The body of the cell as a (possibly multiline) string. |
|
2239 | 2270 | """ |
|
2240 |
fn = self. |
|
|
2271 | fn = self._find_with_lazy_load("cell", magic_name) | |
|
2241 | 2272 | if fn is None: |
|
2242 | 2273 | lm = self.find_line_magic(magic_name) |
|
2243 | 2274 | etpl = "Cell magic `%%{0}` not found{1}." |
@@ -302,6 +302,34 b' class MagicsManager(Configurable):' | |||
|
302 | 302 | # holding the actual callable object as value. This is the dict used for |
|
303 | 303 | # magic function dispatch |
|
304 | 304 | magics = Dict() |
|
305 | lazy_magics = Dict( | |
|
306 | help=""" | |
|
307 | Mapping from magic names to modules to load. | |
|
308 | ||
|
309 | This can be used in IPython/IPykernel configuration to declare lazy magics | |
|
310 | that will only be imported/registered on first use. | |
|
311 | ||
|
312 | For example:: | |
|
313 | ||
|
314 | c.MagicsManger.lazy_magics = { | |
|
315 | "my_magic": "slow.to.import", | |
|
316 | "my_other_magic": "also.slow", | |
|
317 | } | |
|
318 | ||
|
319 | On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or | |
|
320 | `%%my_other_magic`, the corresponding module will be loaded as an ipython | |
|
321 | extensions as if you had previously done `%load_ext ipython`. | |
|
322 | ||
|
323 | Magics names should be without percent(s) as magics can be both cell | |
|
324 | and line magics. | |
|
325 | ||
|
326 | Lazy loading happen relatively late in execution process, and | |
|
327 | complex extensions that manipulate Python/IPython internal state or global state | |
|
328 | might not support lazy loading. | |
|
329 | """ | |
|
330 | ).tag( | |
|
331 | config=True, | |
|
332 | ) | |
|
305 | 333 | |
|
306 | 334 | # A registry of the original objects that we've been given holding magics. |
|
307 | 335 | registry = Dict() |
@@ -366,6 +394,24 b' class MagicsManager(Configurable):' | |||
|
366 | 394 | docs[m_type] = m_docs |
|
367 | 395 | return docs |
|
368 | 396 | |
|
397 | def register_lazy(self, name: str, fully_qualified_name: str): | |
|
398 | """ | |
|
399 | Lazily register a magic via an extension. | |
|
400 | ||
|
401 | ||
|
402 | Parameters | |
|
403 | ---------- | |
|
404 | name : str | |
|
405 | Name of the magic you wish to register. | |
|
406 | fully_qualified_name : | |
|
407 | Fully qualified name of the module/submodule that should be loaded | |
|
408 | as an extensions when the magic is first called. | |
|
409 | It is assumed that loading this extensions will register the given | |
|
410 | magic. | |
|
411 | """ | |
|
412 | ||
|
413 | self.lazy_magics[name] = fully_qualified_name | |
|
414 | ||
|
369 | 415 | def register(self, *magic_objects): |
|
370 | 416 | """Register one or more instances of Magics. |
|
371 | 417 |
@@ -34,9 +34,11 b' from IPython.testing import tools as tt' | |||
|
34 | 34 | from IPython.utils.io import capture_output |
|
35 | 35 | from IPython.utils.process import find_cmd |
|
36 | 36 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory |
|
37 | from IPython.utils.syspathcontext import prepended_to_syspath | |
|
37 | 38 | |
|
38 | 39 | from .test_debugger import PdbTestInput |
|
39 | 40 | |
|
41 | from tempfile import NamedTemporaryFile | |
|
40 | 42 | |
|
41 | 43 | @magic.magics_class |
|
42 | 44 | class DummyMagics(magic.Magics): pass |
@@ -1325,13 +1327,53 b' def test_timeit_arguments():' | |||
|
1325 | 1327 | _ip.magic("timeit -n1 -r1 a=('#')") |
|
1326 | 1328 | |
|
1327 | 1329 | |
|
1330 | MINIMAL_LAZY_MAGIC = """ | |
|
1331 | from IPython.core.magic import ( | |
|
1332 | Magics, | |
|
1333 | magics_class, | |
|
1334 | line_magic, | |
|
1335 | cell_magic, | |
|
1336 | ) | |
|
1337 | ||
|
1338 | ||
|
1339 | @magics_class | |
|
1340 | class LazyMagics(Magics): | |
|
1341 | @line_magic | |
|
1342 | def lazy_line(self, line): | |
|
1343 | print("Lazy Line") | |
|
1344 | ||
|
1345 | @cell_magic | |
|
1346 | def lazy_cell(self, line, cell): | |
|
1347 | print("Lazy Cell") | |
|
1348 | ||
|
1349 | ||
|
1350 | def load_ipython_extension(ipython): | |
|
1351 | ipython.register_magics(LazyMagics) | |
|
1352 | """ | |
|
1353 | ||
|
1354 | ||
|
1355 | def test_lazy_magics(): | |
|
1356 | with pytest.raises(UsageError): | |
|
1357 | ip.run_line_magic("lazy_line", "") | |
|
1358 | ||
|
1359 | startdir = os.getcwd() | |
|
1360 | ||
|
1361 | with TemporaryDirectory() as tmpdir: | |
|
1362 | with prepended_to_syspath(tmpdir): | |
|
1363 | ptempdir = Path(tmpdir) | |
|
1364 | tf = ptempdir / "lazy_magic_module.py" | |
|
1365 | tf.write_text(MINIMAL_LAZY_MAGIC) | |
|
1366 | ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) | |
|
1367 | with tt.AssertPrints("Lazy Line"): | |
|
1368 | ip.run_line_magic("lazy_line", "") | |
|
1369 | ||
|
1370 | ||
|
1328 | 1371 | TEST_MODULE = """ |
|
1329 | 1372 | print('Loaded my_tmp') |
|
1330 | 1373 | if __name__ == "__main__": |
|
1331 | 1374 | print('I just ran a script') |
|
1332 | 1375 | """ |
|
1333 | 1376 | |
|
1334 | ||
|
1335 | 1377 | def test_run_module_from_import_hook(): |
|
1336 | 1378 | "Test that a module can be loaded via an import hook" |
|
1337 | 1379 | with TemporaryDirectory() as tmpdir: |
@@ -9,11 +9,35 b' from io import StringIO' | |||
|
9 | 9 | from unittest import TestCase |
|
10 | 10 | |
|
11 | 11 | from IPython.testing import tools as tt |
|
12 | ||
|
13 | 12 | #----------------------------------------------------------------------------- |
|
14 | 13 | # Test functions begin |
|
15 | 14 | #----------------------------------------------------------------------------- |
|
16 | 15 | |
|
16 | ||
|
17 | MINIMAL_LAZY_MAGIC = """ | |
|
18 | from IPython.core.magic import ( | |
|
19 | Magics, | |
|
20 | magics_class, | |
|
21 | line_magic, | |
|
22 | cell_magic, | |
|
23 | ) | |
|
24 | ||
|
25 | ||
|
26 | @magics_class | |
|
27 | class LazyMagics(Magics): | |
|
28 | @line_magic | |
|
29 | def lazy_line(self, line): | |
|
30 | print("Lazy Line") | |
|
31 | ||
|
32 | @cell_magic | |
|
33 | def lazy_cell(self, line, cell): | |
|
34 | print("Lazy Cell") | |
|
35 | ||
|
36 | ||
|
37 | def load_ipython_extension(ipython): | |
|
38 | ipython.register_magics(LazyMagics) | |
|
39 | """ | |
|
40 | ||
|
17 | 41 | def check_cpaste(code, should_fail=False): |
|
18 | 42 | """Execute code via 'cpaste' and ensure it was executed, unless |
|
19 | 43 | should_fail is set. |
@@ -31,7 +55,7 b' def check_cpaste(code, should_fail=False):' | |||
|
31 | 55 | try: |
|
32 | 56 | context = tt.AssertPrints if should_fail else tt.AssertNotPrints |
|
33 | 57 | with context("Traceback (most recent call last)"): |
|
34 |
|
|
|
58 | ip.run_line_magic("cpaste", "") | |
|
35 | 59 | |
|
36 | 60 | if not should_fail: |
|
37 | 61 | assert ip.user_ns['code_ran'], "%r failed" % code |
@@ -68,13 +92,14 b' def test_cpaste():' | |||
|
68 | 92 | check_cpaste(code, should_fail=True) |
|
69 | 93 | |
|
70 | 94 | |
|
95 | ||
|
71 | 96 | class PasteTestCase(TestCase): |
|
72 | 97 | """Multiple tests for clipboard pasting""" |
|
73 | 98 | |
|
74 | 99 | def paste(self, txt, flags='-q'): |
|
75 | 100 | """Paste input text, by default in quiet mode""" |
|
76 | 101 |
ip.hooks.clipboard_get = lambda |
|
77 |
ip.magic( |
|
|
102 | ip.run_line_magic("paste", flags) | |
|
78 | 103 | |
|
79 | 104 | def setUp(self): |
|
80 | 105 | # Inject fake clipboard hook but save original so we can restore it later |
@@ -114,7 +139,7 b' class PasteTestCase(TestCase):' | |||
|
114 | 139 | self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3]) |
|
115 | 140 | self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9]) |
|
116 | 141 | self.assertFalse("x" in ip.user_ns) |
|
117 | ip.magic("paste -r") | |
|
142 | ip.run_line_magic("paste", "-r") | |
|
118 | 143 | self.assertEqual(ip.user_ns["x"], [1, 2, 3]) |
|
119 | 144 | self.assertEqual(ip.user_ns["y"], [1, 4, 9]) |
|
120 | 145 |
@@ -25,6 +25,7 b' from IPython.core.history import HistoryManager' | |||
|
25 | 25 | from IPython.core.application import ( |
|
26 | 26 | ProfileDir, BaseIPythonApplication, base_flags, base_aliases |
|
27 | 27 | ) |
|
28 | from IPython.core.magic import MagicsManager | |
|
28 | 29 | from IPython.core.magics import ( |
|
29 | 30 | ScriptMagics, LoggingMagics |
|
30 | 31 | ) |
@@ -200,6 +201,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||
|
200 | 201 | self.__class__, # it will also affect subclasses (e.g. QtConsole) |
|
201 | 202 | TerminalInteractiveShell, |
|
202 | 203 | HistoryManager, |
|
204 | MagicsManager, | |
|
203 | 205 | ProfileDir, |
|
204 | 206 | PlainTextFormatter, |
|
205 | 207 | IPCompleter, |
@@ -3,6 +3,24 b'' | |||
|
3 | 3 | ============ |
|
4 | 4 | |
|
5 | 5 | |
|
6 | .. _version 7.32: | |
|
7 | ||
|
8 | IPython 7.32 | |
|
9 | ============ | |
|
10 | ||
|
11 | ||
|
12 | The ability to configure magics to be lazily loaded has been added to IPython. | |
|
13 | See the ``ipython --help-all`` section on ``MagicsManager.lazy_magic``. | |
|
14 | One can now use:: | |
|
15 | ||
|
16 | c.MagicsManger.lazy_magics = { | |
|
17 | "my_magic": "slow.to.import", | |
|
18 | "my_other_magic": "also.slow", | |
|
19 | } | |
|
20 | ||
|
21 | And on first use of ``%my_magic``, or corresponding cell magic, or other line magic, | |
|
22 | the corresponding ``load_ext`` will be called just before trying to invoke the magic. | |
|
23 | ||
|
6 | 24 | .. _version 7.31: |
|
7 | 25 | |
|
8 | 26 | IPython 7.31 |
General Comments 0
You need to be logged in to leave comments.
Login now