From cd387ef61715f61c6ea71f0cfa58ecab5a8a7f78 2021-11-19 01:20:36 From: Nikita Kniazev <nok.raven@gmail.com> Date: 2021-11-19 01:20:36 Subject: [PATCH] Preserve function/method identity in magic decorators `decorator(call, func)` does `func.__doc__ = call.__doc__` and so on, where `func` is the original function and `call = lambda f, *a, **k: f(*a, **k)`. Because of that ipdoctest is not able to see the function original docstring, leading to missing tests. The resulting wrapper is simply calling the original function and thus is not needed. --- diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 6dc0ad3..7f7f1f9 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -20,7 +20,6 @@ from traitlets.config.configurable import Configurable from . import oinspect from .error import UsageError from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2 -from decorator import decorator from ..utils.ipstruct import Struct from ..utils.process import arg_split from ..utils.text import dedent @@ -184,20 +183,18 @@ def _method_magic_marker(magic_kind): # This is a closure to capture the magic_kind. We could also use a class, # but it's overkill for just that one bit of state. def magic_deco(arg): - call = lambda f, *a, **k: f(*a, **k) - if callable(arg): # "Naked" decorator call (just @foo, no args) func = arg name = func.__name__ - retval = decorator(call, func) + retval = arg record_magic(magics, magic_kind, name, name) elif isinstance(arg, str): # Decorator called with arguments (@foo('bar')) name = arg def mark(func, *a, **kw): record_magic(magics, magic_kind, name, func.__name__) - return decorator(call, func) + return func retval = mark else: raise TypeError("Decorator can only be called with " @@ -217,8 +214,6 @@ def _function_magic_marker(magic_kind): # This is a closure to capture the magic_kind. We could also use a class, # but it's overkill for just that one bit of state. def magic_deco(arg): - call = lambda f, *a, **k: f(*a, **k) - # Find get_ipython() in the caller's namespace caller = sys._getframe(1) for ns in ['f_locals', 'f_globals', 'f_builtins']: @@ -236,13 +231,13 @@ def _function_magic_marker(magic_kind): func = arg name = func.__name__ ip.register_magic_function(func, magic_kind, name) - retval = decorator(call, func) + retval = arg elif isinstance(arg, str): # Decorator called with arguments (@foo('bar')) name = arg def mark(func, *a, **kw): ip.register_magic_function(func, magic_kind, name) - return decorator(call, func) + return func retval = mark else: raise TypeError("Decorator can only be called with " diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 72cfc80..4a8f223 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -74,6 +74,7 @@ class BasicMagics(Magics): These are various magics that don't fit into specific categories but that are all part of the base 'IPython experience'.""" + @skip_doctest @magic_arguments.magic_arguments() @magic_arguments.argument( '-l', '--line', action='store_true', diff --git a/IPython/core/magics/config.py b/IPython/core/magics/config.py index b582bdd..25a74e0 100644 --- a/IPython/core/magics/config.py +++ b/IPython/core/magics/config.py @@ -54,44 +54,73 @@ class ConfigMagics(Magics): In [1]: %config Available objects for config: - TerminalInteractiveShell - HistoryManager - PrefilterManager AliasManager - IPCompleter DisplayFormatter + HistoryManager + IPCompleter + LoggingMagics + MagicsManager + OSMagics + PrefilterManager + ScriptMagics + TerminalInteractiveShell To view what is configurable on a given class, just pass the class name:: In [2]: %config IPCompleter - IPCompleter options - ----------------- - IPCompleter.omit__names=<Enum> - Current: 2 - Choices: (0, 1, 2) - Instruct the completer to omit private method names - Specifically, when completing on ``object.<tab>``. - When 2 [default]: all names that start with '_' will be excluded. - When 1: all 'magic' names (``__foo__``) will be excluded. - When 0: nothing will be excluded. - IPCompleter.merge_completions=<CBool> + IPCompleter(Completer) options + ---------------------------- + IPCompleter.backslash_combining_completions=<Bool> + Enable unicode completions, e.g. \\alpha<tab> . Includes completion of latex + commands, unicode names, and expanding unicode characters back to latex + commands. Current: True - Whether to merge completion results into a single list - If False, only the completion results from the first non-empty - completer will be returned. - IPCompleter.limit_to__all__=<CBool> + IPCompleter.debug=<Bool> + Enable debug for the Completer. Mostly print extra information for + experimental jedi integration. + Current: False + IPCompleter.greedy=<Bool> + Activate greedy completion + PENDING DEPRECTION. this is now mostly taken care of with Jedi. + This will enable completion on elements of lists, results of function calls, etc., + but can be unsafe because the code is actually evaluated on TAB. Current: False + IPCompleter.jedi_compute_type_timeout=<Int> + Experimental: restrict time (in milliseconds) during which Jedi can compute types. + Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt + performance by preventing jedi to build its cache. + Current: 400 + IPCompleter.limit_to__all__=<Bool> + DEPRECATED as of version 5.0. Instruct the completer to use __all__ for the completion Specifically, when completing on ``object.<tab>``. When True: only those names in obj.__all__ will be included. When False [default]: the __all__ attribute is ignored - IPCompleter.greedy=<CBool> Current: False - Activate greedy completion - This will enable completion on elements of lists, results of - function calls, etc., but can be unsafe because the code is - actually evaluated on TAB. + IPCompleter.merge_completions=<Bool> + Whether to merge completion results into a single list + If False, only the completion results from the first non-empty + completer will be returned. + Current: True + IPCompleter.omit__names=<Enum> + Instruct the completer to omit private method names + Specifically, when completing on ``object.<tab>``. + When 2 [default]: all names that start with '_' will be excluded. + When 1: all 'magic' names (``__foo__``) will be excluded. + When 0: nothing will be excluded. + Choices: any of [0, 1, 2] + Current: 2 + IPCompleter.profile_completions=<Bool> + If True, emit profiling data for completion subsystem using cProfile. + Current: False + IPCompleter.profiler_output_dir=<Unicode> + Template for path at which to output profile data for completions. + Current: '.completion_profiles' + IPCompleter.use_jedi=<Bool> + Experimental: Use Jedi to generate autocompletions. Default to True if jedi + is installed. + Current: True but the real use is in setting values:: @@ -118,7 +147,7 @@ class ConfigMagics(Magics): # print available configurable names print("Available objects for config:") for name in classnames: - print(" ", name) + print(" ", name) return elif line in classnames: # `%config TerminalInteractiveShell` will print trait info for diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index b9f3b8f..7876e64 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -304,6 +304,8 @@ class ScriptMagics(Magics): # in case it's stuck in uninterruptible sleep. -9 = SIGKILL rc = p.returncode or -9 raise CalledProcessError(rc, cell) + + shebang.__skip_doctest__ = os.name != "posix" def _run_script(self, p, cell, to_close): """callback for running the script in the background""" diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index f2185e4..a3a55fc 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -346,10 +346,15 @@ class InteractiveShellTestCase(unittest.TestCase): "A line magic" # Get info on line magic - lfind = ip._ofind('lmagic') - info = dict(found=True, isalias=False, ismagic=True, - namespace = 'IPython internal', obj= lmagic.__wrapped__, - parent = None) + lfind = ip._ofind("lmagic") + info = dict( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=lmagic, + parent=None, + ) self.assertEqual(lfind, info) def test_ofind_cell_magic(self): @@ -360,10 +365,15 @@ class InteractiveShellTestCase(unittest.TestCase): "A cell magic" # Get info on cell magic - find = ip._ofind('cmagic') - info = dict(found=True, isalias=False, ismagic=True, - namespace = 'IPython internal', obj= cmagic.__wrapped__, - parent = None) + find = ip._ofind("cmagic") + info = dict( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=cmagic, + parent=None, + ) self.assertEqual(find, info) def test_ofind_property_with_error(self): diff --git a/IPython/extensions/storemagic.py b/IPython/extensions/storemagic.py index 9102ea1..b4a8cf8 100644 --- a/IPython/extensions/storemagic.py +++ b/IPython/extensions/storemagic.py @@ -17,6 +17,7 @@ import inspect, os, sys, textwrap from IPython.core.error import UsageError from IPython.core.magic import Magics, magics_class, line_magic +from IPython.testing.skipdoctest import skip_doctest from traitlets import Bool @@ -74,6 +75,7 @@ class StoreMagics(Magics): if self.autorestore: restore_data(self.shell) + @skip_doctest @line_magic def store(self, parameter_s=''): """Lightweight persistence for python variables. @@ -82,6 +84,7 @@ class StoreMagics(Magics): In [1]: l = ['hello',10,'world'] In [2]: %store l + Stored 'l' (list) In [3]: exit (IPython session is closed and started again...) diff --git a/IPython/terminal/magics.py b/IPython/terminal/magics.py index c5d382e..3884223 100644 --- a/IPython/terminal/magics.py +++ b/IPython/terminal/magics.py @@ -11,6 +11,7 @@ import sys from IPython.core.error import TryNext, UsageError from IPython.core.magic import Magics, magics_class, line_magic from IPython.lib.clipboard import ClipboardEmpty +from IPython.testing.skipdoctest import skip_doctest from IPython.utils.text import SList, strip_email_quotes from IPython.utils import py3compat @@ -83,6 +84,7 @@ class TerminalMagics(Magics): self.shell.set_autoindent() print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) + @skip_doctest @line_magic def cpaste(self, parameter_s=''): """Paste & execute a pre-formatted code block from clipboard.