Show More
@@ -631,7 +631,6 b' class InteractiveShell(SingletonConfigurable):' | |||
|
631 | 631 | def __init__(self, ipython_dir=None, profile_dir=None, |
|
632 | 632 | user_module=None, user_ns=None, |
|
633 | 633 | custom_exceptions=((), None), **kwargs): |
|
634 | ||
|
635 | 634 | # This is where traits with a config_key argument are updated |
|
636 | 635 | # from the values on config. |
|
637 | 636 | super(InteractiveShell, self).__init__(**kwargs) |
@@ -1787,7 +1786,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
1787 | 1786 | formatter, |
|
1788 | 1787 | info, |
|
1789 | 1788 | enable_html_pager=self.enable_html_pager, |
|
1790 | **kw | |
|
1789 | **kw, | |
|
1791 | 1790 | ) |
|
1792 | 1791 | else: |
|
1793 | 1792 | pmethod(info.obj, oname) |
@@ -2331,7 +2330,34 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2331 | 2330 | func, magic_kind=magic_kind, magic_name=magic_name |
|
2332 | 2331 | ) |
|
2333 | 2332 | |
|
2334 | def run_line_magic(self, magic_name, line, _stack_depth=1): | |
|
2333 | def _find_with_lazy_load(self, type_, magic_name: str): | |
|
2334 | """ | |
|
2335 | Try to find a magic potentially lazy-loading it. | |
|
2336 | ||
|
2337 | Parameters | |
|
2338 | ---------- | |
|
2339 | ||
|
2340 | type_: "line"|"cell" | |
|
2341 | the type of magics we are trying to find/lazy load. | |
|
2342 | magic_name: str | |
|
2343 | The name of the magic we are trying to find/lazy load | |
|
2344 | ||
|
2345 | ||
|
2346 | Note that this may have any side effects | |
|
2347 | """ | |
|
2348 | finder = {"line": self.find_line_magic, "cell": self.find_cell_magic}[type_] | |
|
2349 | fn = finder(magic_name) | |
|
2350 | if fn is not None: | |
|
2351 | return fn | |
|
2352 | lazy = self.magics_manager.lazy_magics.get(magic_name) | |
|
2353 | if lazy is None: | |
|
2354 | return None | |
|
2355 | ||
|
2356 | self.run_line_magic("load_ext", lazy) | |
|
2357 | res = finder(magic_name) | |
|
2358 | return res | |
|
2359 | ||
|
2360 | def run_line_magic(self, magic_name: str, line, _stack_depth=1): | |
|
2335 | 2361 | """Execute the given line magic. |
|
2336 | 2362 | |
|
2337 | 2363 | Parameters |
@@ -2346,6 +2372,11 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2346 | 2372 | If run_line_magic() is called from magic() then _stack_depth=2. |
|
2347 | 2373 | This is added to ensure backward compatibility for use of 'get_ipython().magic()' |
|
2348 | 2374 | """ |
|
2375 | fn = self._find_with_lazy_load("line", magic_name) | |
|
2376 | if fn is None: | |
|
2377 | lazy = self.magics_manager.lazy_magics.get(magic_name) | |
|
2378 | if lazy: | |
|
2379 | self.run_line_magic("load_ext", lazy) | |
|
2349 | 2380 | fn = self.find_line_magic(magic_name) |
|
2350 | 2381 | if fn is None: |
|
2351 | 2382 | cm = self.find_cell_magic(magic_name) |
@@ -2399,7 +2430,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2399 | 2430 | cell : str |
|
2400 | 2431 | The body of the cell as a (possibly multiline) string. |
|
2401 | 2432 | """ |
|
2402 |
fn = self. |
|
|
2433 | fn = self._find_with_lazy_load("cell", magic_name) | |
|
2403 | 2434 | if fn is None: |
|
2404 | 2435 | lm = self.find_line_magic(magic_name) |
|
2405 | 2436 | etpl = "Cell magic `%%{0}` not found{1}." |
@@ -310,6 +310,34 b' class MagicsManager(Configurable):' | |||
|
310 | 310 | # holding the actual callable object as value. This is the dict used for |
|
311 | 311 | # magic function dispatch |
|
312 | 312 | magics = Dict() |
|
313 | lazy_magics = Dict( | |
|
314 | help=""" | |
|
315 | Mapping from magic names to modules to load. | |
|
316 | ||
|
317 | This can be used in IPython/IPykernel configuration to declare lazy magics | |
|
318 | that will only be imported/registered on first use. | |
|
319 | ||
|
320 | For example:: | |
|
321 | ||
|
322 | c.MagicsManger.lazy_magics = { | |
|
323 | "my_magic": "slow.to.import", | |
|
324 | "my_other_magic": "also.slow", | |
|
325 | } | |
|
326 | ||
|
327 | On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or | |
|
328 | `%%my_other_magic`, the corresponding module will be loaded as an ipython | |
|
329 | extensions as if you had previously done `%load_ext ipython`. | |
|
330 | ||
|
331 | Magics names should be without percent(s) as magics can be both cell | |
|
332 | and line magics. | |
|
333 | ||
|
334 | Lazy loading happen relatively late in execution process, and | |
|
335 | complex extensions that manipulate Python/IPython internal state or global state | |
|
336 | might not support lazy loading. | |
|
337 | """ | |
|
338 | ).tag( | |
|
339 | config=True, | |
|
340 | ) | |
|
313 | 341 | |
|
314 | 342 | # A registry of the original objects that we've been given holding magics. |
|
315 | 343 | registry = Dict() |
@@ -374,6 +402,24 b' class MagicsManager(Configurable):' | |||
|
374 | 402 | docs[m_type] = m_docs |
|
375 | 403 | return docs |
|
376 | 404 | |
|
405 | def register_lazy(self, name: str, fully_qualified_name: str): | |
|
406 | """ | |
|
407 | Lazily register a magic via an extension. | |
|
408 | ||
|
409 | ||
|
410 | Parameters | |
|
411 | ---------- | |
|
412 | name : str | |
|
413 | Name of the magic you wish to register. | |
|
414 | fully_qualified_name : | |
|
415 | Fully qualified name of the module/submodule that should be loaded | |
|
416 | as an extensions when the magic is first called. | |
|
417 | It is assumed that loading this extensions will register the given | |
|
418 | magic. | |
|
419 | """ | |
|
420 | ||
|
421 | self.lazy_magics[name] = fully_qualified_name | |
|
422 | ||
|
377 | 423 | def register(self, *magic_objects): |
|
378 | 424 | """Register one or more instances of Magics. |
|
379 | 425 |
@@ -32,8 +32,15 b' from IPython.utils.io import capture_output' | |||
|
32 | 32 | from IPython.utils.tempdir import (TemporaryDirectory, |
|
33 | 33 | TemporaryWorkingDirectory) |
|
34 | 34 | from IPython.utils.process import find_cmd |
|
35 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory | |
|
36 | from IPython.utils.syspathcontext import prepended_to_syspath | |
|
37 | ||
|
35 | 38 | from .test_debugger import PdbTestInput |
|
36 | 39 | |
|
40 | from tempfile import NamedTemporaryFile | |
|
41 | ||
|
42 | import pytest | |
|
43 | from pathlib import Path | |
|
37 | 44 | |
|
38 | 45 | @magic.magics_class |
|
39 | 46 | class DummyMagics(magic.Magics): pass |
@@ -1236,3 +1243,44 b' def test_timeit_arguments():' | |||
|
1236 | 1243 | # 3.7 optimize no-op statement like above out, and complain there is |
|
1237 | 1244 | # nothing in the for loop. |
|
1238 | 1245 | _ip.magic("timeit -n1 -r1 a=('#')") |
|
1246 | ||
|
1247 | ||
|
1248 | MINIMAL_LAZY_MAGIC = """ | |
|
1249 | from IPython.core.magic import ( | |
|
1250 | Magics, | |
|
1251 | magics_class, | |
|
1252 | line_magic, | |
|
1253 | cell_magic, | |
|
1254 | ) | |
|
1255 | ||
|
1256 | ||
|
1257 | @magics_class | |
|
1258 | class LazyMagics(Magics): | |
|
1259 | @line_magic | |
|
1260 | def lazy_line(self, line): | |
|
1261 | print("Lazy Line") | |
|
1262 | ||
|
1263 | @cell_magic | |
|
1264 | def lazy_cell(self, line, cell): | |
|
1265 | print("Lazy Cell") | |
|
1266 | ||
|
1267 | ||
|
1268 | def load_ipython_extension(ipython): | |
|
1269 | ipython.register_magics(LazyMagics) | |
|
1270 | """ | |
|
1271 | ||
|
1272 | ||
|
1273 | def test_lazy_magics(): | |
|
1274 | with pytest.raises(UsageError): | |
|
1275 | ip.run_line_magic("lazy_line", "") | |
|
1276 | ||
|
1277 | startdir = os.getcwd() | |
|
1278 | ||
|
1279 | with TemporaryDirectory() as tmpdir: | |
|
1280 | with prepended_to_syspath(tmpdir): | |
|
1281 | ptempdir = Path(tmpdir) | |
|
1282 | tf = ptempdir / "lazy_magic_module.py" | |
|
1283 | tf.write_text(MINIMAL_LAZY_MAGIC) | |
|
1284 | ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) | |
|
1285 | with tt.AssertPrints("Lazy Line"): | |
|
1286 | ip.run_line_magic("lazy_line", "") |
@@ -14,11 +14,11 b' from unittest import TestCase' | |||
|
14 | 14 | import nose.tools as nt |
|
15 | 15 | |
|
16 | 16 | from IPython.testing import tools as tt |
|
17 | ||
|
18 | 17 | #----------------------------------------------------------------------------- |
|
19 | 18 | # Test functions begin |
|
20 | 19 | #----------------------------------------------------------------------------- |
|
21 | 20 | |
|
21 | ||
|
22 | 22 | def check_cpaste(code, should_fail=False): |
|
23 | 23 | """Execute code via 'cpaste' and ensure it was executed, unless |
|
24 | 24 | should_fail is set. |
@@ -39,7 +39,7 b' def check_cpaste(code, should_fail=False):' | |||
|
39 | 39 | try: |
|
40 | 40 | context = tt.AssertPrints if should_fail else tt.AssertNotPrints |
|
41 | 41 | with context("Traceback (most recent call last)"): |
|
42 |
|
|
|
42 | ip.run_line_magic("cpaste", "") | |
|
43 | 43 | |
|
44 | 44 | if not should_fail: |
|
45 | 45 | assert ip.user_ns['code_ran'], "%r failed" % code |
@@ -76,13 +76,14 b' def test_cpaste():' | |||
|
76 | 76 | check_cpaste(code, should_fail=True) |
|
77 | 77 | |
|
78 | 78 | |
|
79 | ||
|
79 | 80 | class PasteTestCase(TestCase): |
|
80 | 81 | """Multiple tests for clipboard pasting""" |
|
81 | 82 | |
|
82 | 83 | def paste(self, txt, flags='-q'): |
|
83 | 84 | """Paste input text, by default in quiet mode""" |
|
84 | 85 |
ip.hooks.clipboard_get = lambda |
|
85 |
ip.magic( |
|
|
86 | ip.run_line_magic("paste", flags) | |
|
86 | 87 | |
|
87 | 88 | def setUp(self): |
|
88 | 89 | # Inject fake clipboard hook but save original so we can restore it later |
@@ -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, |
@@ -10,6 +10,24 b' IPython 7.31.1 (CVE-2022-21699)' | |||
|
10 | 10 | Fixed CVE-2022-21699, see IPython 8.0.1 release notes for informations. |
|
11 | 11 | |
|
12 | 12 | |
|
13 | .. _version 7.32: | |
|
14 | ||
|
15 | IPython 7.32 | |
|
16 | ============ | |
|
17 | ||
|
18 | ||
|
19 | The ability to configure magics to be lazily loaded has been added to IPython. | |
|
20 | See the ``ipython --help-all`` section on ``MagicsManager.lazy_magic``. | |
|
21 | One can now use:: | |
|
22 | ||
|
23 | c.MagicsManger.lazy_magics = { | |
|
24 | "my_magic": "slow.to.import", | |
|
25 | "my_other_magic": "also.slow", | |
|
26 | } | |
|
27 | ||
|
28 | And on first use of ``%my_magic``, or corresponding cell magic, or other line magic, | |
|
29 | the corresponding ``load_ext`` will be called just before trying to invoke the magic. | |
|
30 | ||
|
13 | 31 | .. _version 7.31: |
|
14 | 32 | |
|
15 | 33 | IPython 7.31 |
General Comments 0
You need to be logged in to leave comments.
Login now