From 1370a79a44d9b8d72ad4918740fb05ddf75952ab Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Fri, 9 Jun 2017 20:23:31 +0530 Subject: [PATCH 0001/3726] remove deprecated variable - default_gui_banner --- IPython/core/usage.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/IPython/core/usage.py b/IPython/core/usage.py index 2a98a6e4eea..897cf0b3142 100644 --- a/IPython/core/usage.py +++ b/IPython/core/usage.py @@ -339,10 +339,3 @@ ] default_banner = ''.join(default_banner_parts) - -# deprecated GUI banner - -default_gui_banner = '\n'.join([ - 'DEPRECATED: IPython.core.usage.default_gui_banner is deprecated and will be removed', - default_banner, -]) From 1ad6a29546a92684f724ffdd1597476eb06998b8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 12 Sep 2017 12:53:24 -0700 Subject: [PATCH 0002/3726] Avoid crashing if interrupt the kernel during completion. This is likely due to completion taking too long, so just return what we have so far, and ignore the interrupt. closes #10733 --- IPython/core/completer.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 18905b17d2f..ed44d98ce95 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1769,11 +1769,16 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: category=ProvisionalCompleterWarning, stacklevel=2) seen = set() - for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): - if c and (c in seen): - continue - yield c - seen.add(c) + try: + for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): + if c and (c in seen): + continue + yield c + seen.add(c) + except KeyboardInterrupt: + """if completions take too long and users send keyboard interrupt, + do not crash and return ASAP. """ + pass def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Completion]: """ From aa82047d3b476ce645b1f5e3a2205b20cee7e258 Mon Sep 17 00:00:00 2001 From: Lesley Cordero Date: Fri, 10 Nov 2017 16:20:36 -0500 Subject: [PATCH 0003/3726] document change for ipython membedding global namespace --- docs/source/interactive/reference.rst | 74 +++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/docs/source/interactive/reference.rst b/docs/source/interactive/reference.rst index ef49565bf61..c3acbc0e0da 100644 --- a/docs/source/interactive/reference.rst +++ b/docs/source/interactive/reference.rst @@ -609,10 +609,76 @@ python, see :ref:`configure_start_ipython`. It is also possible to embed an IPython shell in a namespace in your Python code. This allows you to evaluate dynamically the state of your code, -operate with your variables, analyze them, etc. Note however that -any changes you make to values while in the shell do not propagate back -to the running code, so it is safe to modify your values because you -won't break your code in bizarre ways by doing so. +operate with your variables, analyze them, etc. For example, if you run the +following code snippet: + +``` python +import IPython + +a = 42 +IPython.embed() +``` + +and within the IPython shell, you reassign `a` to `23` to do further testing of +some sort, you can then exit. + +``` +>>> IPython.embed() +Python 3.6.2 (default, Jul 17 2017, 16:44:45) +Type 'copyright', 'credits' or 'license' for more information +IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + +In [1]: a = 23 + +In [2]: exit() +``` + +Once you exit and print `a`, the value 23 will be returned: + + +``` python +print(a) +``` +``` +23 +``` + +It's important to note that the code run in the embedded IPython shell will +*not* change the state of your code and variables, **unless** the shell is +contained within the global namespace. In the above example, `a` is changed +because this is true. + +To further exemplify this, consider the following example: + +``` python +import IPython +def do(): + a = 42 + print(a) + IPython.embed() + print(a) +``` + +Now if call the function and complete the state changes as we did above, the +value `42` will be returned. Again, this is because it's not in the global namespace. + +``` python +do() +``` +``` +42 +>>> IPython.embed() +Python 3.6.2 (default, Jul 17 2017, 16:44:45) +Type 'copyright', 'credits' or 'license' for more information +IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + +In [1]: a = 23 + +In [2]: exit() +``` +``` +42 +``` .. note:: From 4da4e514618458d89d8adb216e57bd49bafb85c6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 11 Nov 2017 10:10:39 -0800 Subject: [PATCH 0004/3726] 'fix RST markup' --- docs/source/interactive/reference.rst | 99 ++++++++++++--------------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/docs/source/interactive/reference.rst b/docs/source/interactive/reference.rst index c3acbc0e0da..497a27496d4 100644 --- a/docs/source/interactive/reference.rst +++ b/docs/source/interactive/reference.rst @@ -607,78 +607,65 @@ startup files, and everything, just as if it were a normal IPython session. For information on setting configuration options when running IPython from python, see :ref:`configure_start_ipython`. -It is also possible to embed an IPython shell in a namespace in your Python code. -This allows you to evaluate dynamically the state of your code, -operate with your variables, analyze them, etc. For example, if you run the -following code snippet: +It is also possible to embed an IPython shell in a namespace in your Python +code. This allows you to evaluate dynamically the state of your code, operate +with your variables, analyze them, etc. For example, if you run the following +code snippet:: -``` python -import IPython + import IPython -a = 42 -IPython.embed() -``` + a = 42 + IPython.embed() and within the IPython shell, you reassign `a` to `23` to do further testing of -some sort, you can then exit. +some sort, you can then exit:: -``` ->>> IPython.embed() -Python 3.6.2 (default, Jul 17 2017, 16:44:45) -Type 'copyright', 'credits' or 'license' for more information -IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + >>> IPython.embed() + Python 3.6.2 (default, Jul 17 2017, 16:44:45) + Type 'copyright', 'credits' or 'license' for more information + IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. -In [1]: a = 23 + In [1]: a = 23 -In [2]: exit() -``` + In [2]: exit() -Once you exit and print `a`, the value 23 will be returned: +Once you exit and print `a`, the value 23 will be returned:: -``` python -print(a) -``` -``` -23 -``` + In: print(a) + 23 It's important to note that the code run in the embedded IPython shell will *not* change the state of your code and variables, **unless** the shell is contained within the global namespace. In the above example, `a` is changed because this is true. -To further exemplify this, consider the following example: - -``` python -import IPython -def do(): - a = 42 - print(a) - IPython.embed() - print(a) -``` - -Now if call the function and complete the state changes as we did above, the -value `42` will be returned. Again, this is because it's not in the global namespace. - -``` python -do() -``` -``` -42 ->>> IPython.embed() -Python 3.6.2 (default, Jul 17 2017, 16:44:45) -Type 'copyright', 'credits' or 'license' for more information -IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. - -In [1]: a = 23 - -In [2]: exit() -``` -``` -42 -``` +To further exemplify this, consider the following example:: + + import IPython + def do(): + a = 42 + print(a) + IPython.embed() + print(a) + +Now if call the function and complete the state changes as we did above, the +value `42` will be returned. Again, this is because it's not in the global +namespace:: + + do() + +Running a file with the above code can lead to the following session:: + + >>> IPython.embed() + Python 3.6.2 (default, Jul 17 2017, 16:44:45) + Type 'copyright', 'credits' or 'license' for more information + IPython 6.2.0.dev -- An enhanced Interactive Python. Type '?' for help. + + In [1]: a = 23 + + In [2]: exit() + 42 .. note:: From db35a8bde3ecd728fd1071c4fd883cc106431ae5 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Mon, 18 Dec 2017 15:04:15 -0500 Subject: [PATCH 0005/3726] Avoid PyGIDeprecationWarning when using Gtk3 backend. Since pygobject 3.7.3, which was released ~5 years ago, the function `GLib.io_add_watch` has preferred a call signature other than the one that our input backends were using. --- IPython/lib/inputhookgtk3.py | 2 +- IPython/terminal/pt_inputhooks/gtk3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/lib/inputhookgtk3.py b/IPython/lib/inputhookgtk3.py index 531f5cae14c..d897e193fe5 100644 --- a/IPython/lib/inputhookgtk3.py +++ b/IPython/lib/inputhookgtk3.py @@ -29,6 +29,6 @@ def _main_quit(*args, **kwargs): def inputhook_gtk3(): - GLib.io_add_watch(sys.stdin, GLib.IO_IN, _main_quit) + GLib.io_add_watch(sys.stdin, GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) Gtk.main() return 0 diff --git a/IPython/terminal/pt_inputhooks/gtk3.py b/IPython/terminal/pt_inputhooks/gtk3.py index 5c6c545457b..ae82b4edaaa 100644 --- a/IPython/terminal/pt_inputhooks/gtk3.py +++ b/IPython/terminal/pt_inputhooks/gtk3.py @@ -8,5 +8,5 @@ def _main_quit(*args, **kwargs): return False def inputhook(context): - GLib.io_add_watch(context.fileno(), GLib.IO_IN, _main_quit) + GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) Gtk.main() From 1af5a3278653bba2d2cc80f369caf57aaf7f8f7d Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 20 Dec 2017 11:36:06 +0000 Subject: [PATCH 0006/3726] Swap the check for Jedi versions around --- IPython/core/completer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 0d54dc7c78d..ec39c0307c3 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1351,11 +1351,11 @@ def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): try: # should we check the type of the node is Error ? try: - # jedi >= 0.11 - from parso.tree import ErrorLeaf - except ImportError: # jedi < 0.11 from jedi.parser.tree import ErrorLeaf + except ImportError: + # jedi >= 0.11 + from parso.tree import ErrorLeaf next_to_last_tree = interpreter._get_module().tree_node.children[-2] completing_string = False From 89e72e56416d8356bacd941f8ae444f116c15917 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 20 Dec 2017 12:52:24 +0000 Subject: [PATCH 0007/3726] Add check for exe-in-virtualenv using os.path.samefile() Closes gh-10955 --- IPython/core/interactiveshell.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 333d1c0facb..115e9319ffd 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -732,16 +732,23 @@ def init_virtualenv(self): # Not in a virtualenv return - # venv detection: + p = os.path.normcase(sys.executable) + p_venv = os.path.normcase(os.environ['VIRTUAL_ENV']) + + # executable path should end like /bin/python or \\scripts\\python.exe + p_exe_up2 = os.path.dirname(os.path.dirname(p)) + if p_exe_up2 and os.path.samefile(p_exe_up2, p_venv): + # Our exe is inside the virtualenv, don't need to do anything. + return + + # fallback venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, so we can't just use sys.executable. # So we just check every item in the symlink tree (generally <= 3) - p = os.path.normcase(sys.executable) paths = [p] while os.path.islink(p): p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p))) paths.append(p) - p_venv = os.path.normcase(os.environ['VIRTUAL_ENV']) # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible if p_venv.startswith('\\cygdrive'): From 324eddd415640132c8d599479dddc5bb5768099d Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 21 Dec 2017 11:46:28 +0000 Subject: [PATCH 0008/3726] Don't sort dicts for display in Python >= 3.7 Closes gh-10110 --- IPython/lib/pretty.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index cbbb7260056..6787d388e77 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -95,6 +95,7 @@ def _repr_pretty_(self, p, cycle): MAX_SEQ_LENGTH = 1000 +DICT_IS_ORDERED = sys.version_info >= (3, 7) _re_pattern_type = type(re.compile('')) def _safe_getattr(obj, attr, default=None): @@ -613,7 +614,9 @@ def inner(obj, p, cycle): p.begin_group(step, start) keys = obj.keys() # if dict isn't large enough to be truncated, sort keys before displaying - if not (p.max_seq_length and len(obj) >= p.max_seq_length): + # From Python 3.7, dicts preserve order by definition, so we don't sort. + if not DICT_IS_ORDERED \ + and not (p.max_seq_length and len(obj) >= p.max_seq_length): keys = _sorted_for_pprint(keys) for idx, key in p._enumerate(keys): if idx: From 9fbb32b9dc7fc7075aae6b7be7c1b0642abbb8f0 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 21 Dec 2017 13:24:44 +0000 Subject: [PATCH 0009/3726] Ensure that __repr__() methods override pretty printers for parent classes Closes gh-10950 --- IPython/lib/pretty.py | 4 ++++ IPython/lib/tests/test_pretty.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index cbbb7260056..af7feb92687 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -392,6 +392,10 @@ def pretty(self, obj): meth = cls._repr_pretty_ if callable(meth): return meth(obj, self, cycle) + if cls is not object \ + and callable(cls.__dict__.get('__repr__')): + return _repr_pprint(obj, self, cycle) + return _default_pprint(obj, self, cycle) finally: self.end_group() diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 6d6574345d5..0229dbe26ab 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -420,4 +420,18 @@ def meaning_of_life(question=None): return "Don't panic" nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life)) - + + +class OrderedCounter(Counter, OrderedDict): + 'Counter that remembers the order elements are first encountered' + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, OrderedDict(self)) + + def __reduce__(self): + return self.__class__, (OrderedDict(self),) + +def test_custom_repr(): + """A custom repr should override a pretty printer for a parent type""" + oc = OrderedCounter("abracadabra") + nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc)) From 0ff4a21cf9ed442f97b59117b7d6fe491a27bce1 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 21 Dec 2017 13:31:53 +0000 Subject: [PATCH 0010/3726] Remove unnecessary code checking for __repr__ on a subclass of builtin types This was added in PR #452, but I believe the previous change makes it redundant. --- IPython/lib/pretty.py | 33 +++++++++----------------------- IPython/lib/tests/test_pretty.py | 6 ++++++ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index af7feb92687..9181113e384 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -541,17 +541,12 @@ def _default_pprint(obj, p, cycle): p.end_group(1, '>') -def _seq_pprinter_factory(start, end, basetype): +def _seq_pprinter_factory(start, end): """ Factory that returns a pprint function useful for sequences. Used by the default pprint for tuples, dicts, and lists. """ def inner(obj, p, cycle): - typ = type(obj) - if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: - # If the subclass provides its own repr, use it instead. - return p.text(typ.__repr__(obj)) - if cycle: return p.text(start + '...' + end) step = len(start) @@ -568,21 +563,16 @@ def inner(obj, p, cycle): return inner -def _set_pprinter_factory(start, end, basetype): +def _set_pprinter_factory(start, end): """ Factory that returns a pprint function useful for sets and frozensets. """ def inner(obj, p, cycle): - typ = type(obj) - if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: - # If the subclass provides its own repr, use it instead. - return p.text(typ.__repr__(obj)) - if cycle: return p.text(start + '...' + end) if len(obj) == 0: # Special case. - p.text(basetype.__name__ + '()') + p.text(type(obj).__name__ + '()') else: step = len(start) p.begin_group(step, start) @@ -600,17 +590,12 @@ def inner(obj, p, cycle): return inner -def _dict_pprinter_factory(start, end, basetype=None): +def _dict_pprinter_factory(start, end): """ Factory that returns a pprint function used by the default pprint of dicts and dict proxies. """ def inner(obj, p, cycle): - typ = type(obj) - if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: - # If the subclass provides its own repr, use it instead. - return p.text(typ.__repr__(obj)) - if cycle: return p.text('{...}') step = len(start) @@ -749,12 +734,12 @@ def _exception_pprint(obj, p, cycle): int: _repr_pprint, float: _repr_pprint, str: _repr_pprint, - tuple: _seq_pprinter_factory('(', ')', tuple), - list: _seq_pprinter_factory('[', ']', list), - dict: _dict_pprinter_factory('{', '}', dict), + tuple: _seq_pprinter_factory('(', ')'), + list: _seq_pprinter_factory('[', ']'), + dict: _dict_pprinter_factory('{', '}'), - set: _set_pprinter_factory('{', '}', set), - frozenset: _set_pprinter_factory('frozenset({', '})', frozenset), + set: _set_pprinter_factory('{', '}'), + frozenset: _set_pprinter_factory('frozenset({', '})'), super: _super_pprint, _re_pattern_type: _re_pattern_pprint, type: _type_pprint, diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 0229dbe26ab..68e90ecae5b 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -431,7 +431,13 @@ def __repr__(self): def __reduce__(self): return self.__class__, (OrderedDict(self),) +class MySet(set): # Override repr of a basic type + def __repr__(self): + return 'mine' + def test_custom_repr(): """A custom repr should override a pretty printer for a parent type""" oc = OrderedCounter("abracadabra") nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc)) + + nt.assert_equal(pretty.pretty(MySet()), 'mine') From febe9ec4f633201b68855f3a39e0eaeafec16a3f Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 21 Dec 2017 13:44:48 +0000 Subject: [PATCH 0011/3726] Treat dicts as ordered from Python 3.6 --- IPython/lib/pretty.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 6787d388e77..5f2520c72e3 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -95,7 +95,9 @@ def _repr_pretty_(self, p, cycle): MAX_SEQ_LENGTH = 1000 -DICT_IS_ORDERED = sys.version_info >= (3, 7) +# The language spec says that dicts preserve order from 3.7, but CPython +# does so from 3.6, so it seems likely that people will expect that. +DICT_IS_ORDERED = sys.version_info >= (3, 6) _re_pattern_type = type(re.compile('')) def _safe_getattr(obj, attr, default=None): From 98c4cb9b8f6ec1c5a2e73d0adb2619a9acdafdf9 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 21 Dec 2017 14:15:07 +0000 Subject: [PATCH 0012/3726] Update test for displaying subclass instance --- IPython/core/tests/test_formatters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 35edc75d19f..cde43c94a92 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -49,7 +49,7 @@ def test_pretty(): f = PlainTextFormatter() f.for_type(A, foo_printer) nt.assert_equal(f(A()), 'foo') - nt.assert_equal(f(B()), 'foo') + nt.assert_equal(f(B()), 'B()') nt.assert_equal(f(GoodPretty()), 'foo') # Just don't raise an exception for the following: f(BadPretty()) From 2578fa7b4193cfb5a021ee7af5c6d2811659b0b0 Mon Sep 17 00:00:00 2001 From: Sjoerd de Vries Date: Sat, 23 Dec 2017 12:26:41 +0100 Subject: [PATCH 0013/3726] added --isolated option for %%html magic --- IPython/core/magics/display.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/IPython/core/magics/display.py b/IPython/core/magics/display.py index ee8c127a90d..07853944715 100644 --- a/IPython/core/magics/display.py +++ b/IPython/core/magics/display.py @@ -16,6 +16,7 @@ from IPython.core.magic import ( Magics, magics_class, cell_magic ) +from IPython.core import magic_arguments #----------------------------------------------------------------------------- # Magic implementation classes @@ -33,7 +34,7 @@ class DisplayMagics(Magics): @cell_magic def js(self, line, cell): """Run the cell block of Javascript code - + Alias of `%%javascript` """ self.javascript(line, cell) @@ -59,12 +60,23 @@ def svg(self, line, cell): """Render the cell as an SVG literal""" display(SVG(cell)) + @magic_arguments.magic_arguments() + @magic_arguments.argument( + '--isolated', action='store_true', default=False, + help="""Annotate the cell as 'isolated'. +Isolated cells are rendered inside their own \n", @@ -3075,7 +3075,7 @@ ], "source": [ "from IPython.display import IFrame\n", - "IFrame('http://jupyter.org', width='100%', height=350)" + "IFrame('https://jupyter.org', width='100%', height=350)" ] }, { @@ -3263,7 +3263,7 @@ "* When you open a notebook, rich output is only displayed if it doesn't contain security vulberabilities, ...\n", "* ... or if you have trusted a notebook, all rich output will run upon opening it.\n", "\n", - "A full description of the IPython security model can be found on [this page](http://ipython.org/ipython-doc/dev/notebook/security.html)." + "A full description of the IPython security model can be found on [this page](https://ipython.org/ipython-doc/dev/notebook/security.html)." ] }, { diff --git a/examples/Index.ipynb b/examples/Index.ipynb index 0e463d48adf..9ee21e2b00d 100644 --- a/examples/Index.ipynb +++ b/examples/Index.ipynb @@ -18,7 +18,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This directory contains IPython's notebook-based documentation. This augments our [Sphinx-based documentation](http://ipython.org/ipython-doc/stable/index.html) with notebooks that contain interactive tutorials and examples. Over time, more of our documentation will be pulled into this format." + "This directory contains IPython's notebook-based documentation. This + augments our [Sphinx-based documentation](https://ipython.org/ipython-doc/stable/index.html) with notebooks that contain interactive tutorials and examples. Over time, more of our documentation will be pulled into this format." ] }, { diff --git a/tools/tests/Confined Output.ipynb b/tools/tests/Confined Output.ipynb index 8ba354d6ec9..f767c19a37f 100644 --- a/tools/tests/Confined Output.ipynb +++ b/tools/tests/Confined Output.ipynb @@ -183,7 +183,7 @@ " \n", @@ -199,7 +199,7 @@ } ], "source": [ - "IFrame(src=\"http://ipython.org\", width=900, height=400)" + "IFrame(src=\"https://ipython.org\", width=900, height=400)" ] }, { diff --git a/tools/tests/Markdown Pandoc Limitations.ipynb b/tools/tests/Markdown Pandoc Limitations.ipynb index ff232bb689b..671aefb7ffd 100644 --- a/tools/tests/Markdown Pandoc Limitations.ipynb +++ b/tools/tests/Markdown Pandoc Limitations.ipynb @@ -1993,7 +1993,7 @@ "\n", "var mdcell = new IPython.MarkdownCell();\n", "mdcell.create_element();\n", - "mdcell.set_text('\\n![Alternate Text](http://ipython.org/_static/IPy_header.png)\\n');\n", + "mdcell.set_text('\\n![Alternate Text](https://ipython.org/_static/IPy_header.png)\\n');\n", "mdcell.render();\n", "$(element).append(mdcell.element)\n", ".removeClass()\n", @@ -2022,10 +2022,10 @@ "text/html": [ "
NBConvert Latex Output
\\begin{figure}[htbp]\n",
        "\\centering\n",
-       "\\includegraphics{http://ipython.org/_static/IPy_header.png}\n",
+       "\\includegraphics{https://ipython.org/_static/IPy_header.png}\n",
        "\\caption{Alternate Text}\n",
        "\\end{figure}
NBViewer Output
\n", - "\"Alternate

Alternate Text

\n", + "\"Alternate

Alternate Text

\n", "
" ], "text/plain": [ @@ -2051,7 +2051,7 @@ ], "source": [ "compare_render(r\"\"\"\n", - "![Alternate Text](http://ipython.org/_static/IPy_header.png)\n", + "![Alternate Text](https://ipython.org/_static/IPy_header.png)\n", "\"\"\")" ] }, @@ -2075,7 +2075,7 @@ "\n", "var mdcell = new IPython.MarkdownCell();\n", "mdcell.create_element();\n", - "mdcell.set_text('\\n\\n');\n", + "mdcell.set_text('\\n\\n');\n", "mdcell.render();\n", "$(element).append(mdcell.element)\n", ".removeClass()\n", @@ -2102,7 +2102,7 @@ { "data": { "text/html": [ - "
NBConvert Latex Output
NBViewer Output

" + "
NBConvert Latex Output
NBViewer Output

" ], "text/plain": [ "" @@ -2127,7 +2127,7 @@ ], "source": [ "compare_render(r\"\"\"\n", - "\n", + "\n", "\"\"\")" ] }, From 4c38d60286ff8a21e6063af8bd0ce72d0cd117b9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 28 Jul 2018 16:11:44 -0700 Subject: [PATCH 0200/3726] what's new in 6.5 --- docs/source/whatsnew/version6.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/source/whatsnew/version6.rst b/docs/source/whatsnew/version6.rst index 49bc0e6eac0..b38b46d6788 100644 --- a/docs/source/whatsnew/version6.rst +++ b/docs/source/whatsnew/version6.rst @@ -2,6 +2,18 @@ 6.x Series ============ +.. _whatsnew650: + +IPython 6.5.0 +============= + +Miscellaneous bug fixes and compatibility with Python 3.7. + +* Autocompletion fix for modules with out ``__init__.py`` :ghpull:`11227` +* update the ``%pastbin`` magic to use ``dpaste.com`` instead og GitHub Gist + which now require authentication :ghpull:`11182` +* Fix crash with multiprocessing :ghpull:`11185` + .. _whatsnew640: IPython 6.4.0 From ad1d785e15e71c09e20b12357d9e6f2975d0d7c5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 28 Jul 2018 17:19:01 -0700 Subject: [PATCH 0201/3726] fix release instructions --- docs/source/coredev/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/coredev/index.rst b/docs/source/coredev/index.rst index 106d2dd70b4..5cb5b178a10 100644 --- a/docs/source/coredev/index.rst +++ b/docs/source/coredev/index.rst @@ -34,7 +34,7 @@ backport to. .. note:: - The ``@`` and ``[dev]`` when mentioning the bot should be optional and can + The ``@`` and ``[bot]`` when mentioning the bot should be optional and can be omitted. If the pull request cannot be automatically backported, the bot should tell you @@ -44,7 +44,7 @@ so on the PR and apply a "Need manual backport" tag to the origin PR. Backport with ghpro ------------------- -We can also use `ghpro ` +We can also use `ghpro `_ to automatically list and apply the PR on other branches. For example: .. code-block:: bash From 6841ce8574c1a72c5690517b82c93484fb830054 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 29 Jul 2018 10:29:41 -0700 Subject: [PATCH 0202/3726] Update version6.rst --- docs/source/whatsnew/version6.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/whatsnew/version6.rst b/docs/source/whatsnew/version6.rst index b38b46d6788..461132ecfce 100644 --- a/docs/source/whatsnew/version6.rst +++ b/docs/source/whatsnew/version6.rst @@ -10,8 +10,8 @@ IPython 6.5.0 Miscellaneous bug fixes and compatibility with Python 3.7. * Autocompletion fix for modules with out ``__init__.py`` :ghpull:`11227` -* update the ``%pastbin`` magic to use ``dpaste.com`` instead og GitHub Gist - which now require authentication :ghpull:`11182` +* update the ``%pastebin`` magic to use ``dpaste.com`` instead og GitHub Gist + which now requires authentication :ghpull:`11182` * Fix crash with multiprocessing :ghpull:`11185` .. _whatsnew640: From 003150e56952217b268f9e2691e07f687c4d9296 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 2 Aug 2018 16:54:53 -0700 Subject: [PATCH 0203/3726] Use raw string when invalid escape sequence present. Those emit deprecation warnings at import/compile time so unfortunately we can't trap that with iptest as modules are already imported. The way to test that is to start IPython via `python -We -m IPython`, and we should try to likely do that in travis. But first fix all places that raise an error --- IPython/core/debugger.py | 2 +- IPython/utils/path.py | 2 +- IPython/utils/text.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 0e0d40111c6..4ece380bf9c 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -176,7 +176,7 @@ def __call__(self): self.debugger.set_trace(sys._getframe().f_back) -RGX_EXTRA_INDENT = re.compile('(?<=\n)\s+') +RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') def strip_indentation(multiline_string): diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 08806c26eeb..f8665975a1a 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -202,7 +202,7 @@ def get_home_dir(require_writable=False): import _winreg as wreg # Py 2 key = wreg.OpenKey( wreg.HKEY_CURRENT_USER, - "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" ) homedir = wreg.QueryValueEx(key,'Personal')[0] key.Close() diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 98d72f48f0a..0c0d82f6323 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -588,7 +588,7 @@ class DollarFormatter(FullEvalFormatter): In [4]: f.format('$a or {b}', a=1, b=2) Out[4]: '1 or 2' """ - _dollar_pattern_ignore_single_quote = re.compile("(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)") + _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)") def parse(self, fmt_string): for literal_txt, field_name, format_spec, conversion \ in Formatter.parse(self, fmt_string): From a6179e397ae89a3897363d59a3a8691ba0ba0a91 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 2 Aug 2018 17:07:24 -0700 Subject: [PATCH 0204/3726] Fix more escape sequences --- IPython/core/completer.py | 2 +- IPython/core/inputsplitter.py | 2 +- IPython/core/inputtransformer.py | 4 ++-- IPython/core/magics/config.py | 2 +- IPython/core/splitinput.py | 4 ++-- IPython/utils/tokenize2.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index fbc2535504d..f09025df34d 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1592,7 +1592,7 @@ def get_keys(obj): $ ''' regexps = self.__dict_key_regexps = { - False: re.compile(dict_key_re_fmt % ''' + False: re.compile(dict_key_re_fmt % r''' # identifiers separated by . (?!\d)\w+ (?:\.(?!\d)\w+)* diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index 337b27d7ac2..2ed02a63f4d 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -65,7 +65,7 @@ # regexp to match pure comment lines so we don't accidentally insert 'if 1:' # before pure comments -comment_line_re = re.compile('^\s*\#') +comment_line_re = re.compile(r'^\s*\#') def num_ini_spaces(s): diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py index 2b275666725..7855bdefb02 100644 --- a/IPython/core/inputtransformer.py +++ b/IPython/core/inputtransformer.py @@ -171,7 +171,7 @@ def output(self, tokens): @CoroutineInputTransformer.wrap def assemble_logical_lines(): - """Join lines following explicit line continuations (\)""" + r"""Join lines following explicit line continuations (\)""" line = '' while True: line = (yield line) @@ -361,7 +361,7 @@ def cellmagic(end_on_blank_line=False): reset (sent None). """ tpl = 'get_ipython().run_cell_magic(%r, %r, %r)' - cellmagic_help_re = re.compile('%%\w+\?') + cellmagic_help_re = re.compile(r'%%\w+\?') line = '' while True: line = (yield line) diff --git a/IPython/core/magics/config.py b/IPython/core/magics/config.py index 044614b3908..97b13df02e6 100644 --- a/IPython/core/magics/config.py +++ b/IPython/core/magics/config.py @@ -24,7 +24,7 @@ # Magic implementation classes #----------------------------------------------------------------------------- -reg = re.compile('^\w+\.\w+$') +reg = re.compile(r'^\w+\.\w+$') @magics_class class ConfigMagics(Magics): diff --git a/IPython/core/splitinput.py b/IPython/core/splitinput.py index f8bf62308a6..63cdce79558 100644 --- a/IPython/core/splitinput.py +++ b/IPython/core/splitinput.py @@ -41,7 +41,7 @@ # ! and !! trigger if they are first char(s) *or* follow an indent # ? triggers as first or last char. -line_split = re.compile(""" +line_split = re.compile(r""" ^(\s*) # any leading space ([,;/%]|!!?|\?\??)? # escape character or characters \s*(%{0,2}[\w\.\*]*) # function/method, possibly with leading % @@ -68,7 +68,7 @@ def split_user_input(line, pattern=None): except ValueError: # print "split failed for line '%s'" % line ifun, the_rest = line, u'' - pre = re.match('^(\s*)(.*)',line).groups()[0] + pre = re.match(r'^(\s*)(.*)',line).groups()[0] esc = "" else: pre, esc, ifun, the_rest = match.groups() diff --git a/IPython/utils/tokenize2.py b/IPython/utils/tokenize2.py index 97ac18de37f..1448b5ccbd5 100644 --- a/IPython/utils/tokenize2.py +++ b/IPython/utils/tokenize2.py @@ -47,7 +47,7 @@ from codecs import lookup, BOM_UTF8 import collections from io import TextIOWrapper -cookie_re = re.compile("coding[:=]\s*([-\w.]+)") +cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)") import token __all__ = token.__all__ + ["COMMENT", "tokenize", "detect_encoding", From ba719e0d7c3f395d6b33f91a8fecc88f97c37c8f Mon Sep 17 00:00:00 2001 From: Peter Parente Date: Sat, 4 Aug 2018 16:06:04 -0400 Subject: [PATCH 0205/3726] Fix breaking newline --- examples/Index.ipynb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/Index.ipynb b/examples/Index.ipynb index 9ee21e2b00d..30e201a7e31 100644 --- a/examples/Index.ipynb +++ b/examples/Index.ipynb @@ -18,8 +18,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This directory contains IPython's notebook-based documentation. This - augments our [Sphinx-based documentation](https://ipython.org/ipython-doc/stable/index.html) with notebooks that contain interactive tutorials and examples. Over time, more of our documentation will be pulled into this format." + "This directory contains IPython's notebook-based documentation. This augments our [Sphinx-based documentation](https://ipython.org/ipython-doc/stable/index.html) with notebooks that contain interactive tutorials and examples. Over time, more of our documentation will be pulled into this format." ] }, { From 29b75abae86bd186a91399518bbab6b9d36cf404 Mon Sep 17 00:00:00 2001 From: Alyssa Whitwell Date: Sat, 4 Aug 2018 13:42:22 -0700 Subject: [PATCH 0206/3726] Change signature to be imported from inspect library --- IPython/lib/pretty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index aeaed929825..90a8c78eece 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -85,12 +85,12 @@ def _repr_pretty_(self, p, cycle): import sys import types from collections import deque +from inspect import signature from io import StringIO from warnings import warn from IPython.utils.decorators import undoc from IPython.utils.py3compat import PYPY -from IPython.utils.signatures import signature __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', 'for_type', 'for_type_by_name'] From 0854ec5970d28cfb324c021cbddda004ee57b0d8 Mon Sep 17 00:00:00 2001 From: Alyssa Whitwell Date: Sat, 4 Aug 2018 14:25:40 -0700 Subject: [PATCH 0207/3726] Add .python-version to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 533958514c3..f3141570400 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ __pycache__ *.swp .vscode .pytest_cache +.python-version From d697c122d091d53e9589e9a2dcb05b205073644f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 2 Aug 2018 17:07:48 -0700 Subject: [PATCH 0208/3726] Update deprecation warning message --- IPython/utils/signatures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/utils/signatures.py b/IPython/utils/signatures.py index c4d9d1bf86e..88d72b185eb 100644 --- a/IPython/utils/signatures.py +++ b/IPython/utils/signatures.py @@ -5,7 +5,8 @@ """ import warnings -warnings.warn("{} backport for Python 2 is deprecated in IPython 6, which only supports Python 3".format(__name__), +warnings.warn("{} backport for Python 2 is deprecated in IPython 6, which only supports " + "Python 3. Import directly from standard library `inspect`".format(__name__), DeprecationWarning, stacklevel=2) from inspect import BoundArguments, Parameter, Signature, signature From 2e9fe8cd4ac23230d9e74e2c04bad8ba65d416c2 Mon Sep 17 00:00:00 2001 From: Alyssa Whitwell Date: Sat, 4 Aug 2018 16:04:18 -0700 Subject: [PATCH 0209/3726] Deprecate use of imp library, condense module_paths module to only one function, update tests --- IPython/utils/module_paths.py | 98 ++++++------------------ IPython/utils/tests/test_module_paths.py | 64 ++++++---------- 2 files changed, 44 insertions(+), 118 deletions(-) diff --git a/IPython/utils/module_paths.py b/IPython/utils/module_paths.py index f98458098f5..d50df8055a7 100644 --- a/IPython/utils/module_paths.py +++ b/IPython/utils/module_paths.py @@ -2,14 +2,7 @@ Utility functions for finding modules on sys.path. -`find_mod` finds named module on sys.path. - -`get_init` helper function that finds __init__ file in a directory. - -`find_module` variant of imp.find_module in std_lib that only returns -path to module and not an open file object as well. - - +`find_module` returns a path to module or None, given certain conditions. """ #----------------------------------------------------------------------------- @@ -25,7 +18,7 @@ #----------------------------------------------------------------------------- # Stdlib imports -import imp +import importlib import os # Third-party imports @@ -44,81 +37,34 @@ #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- -def find_module(name, path=None): - """imp.find_module variant that only return path of module. - - The `imp.find_module` returns a filehandle that we are not interested in. - Also we ignore any bytecode files that `imp.find_module` finds. - - Parameters - ---------- - name : str - name of module to locate - path : list of str - list of paths to search for `name`. If path=None then search sys.path - Returns - ------- - filename : str - Return full path of module or None if module is missing or does not have - .py or .pyw extension - """ - if name is None: - return None - try: - file, filename, _ = imp.find_module(name, path) - except ImportError: - return None - if file is None: - return filename - else: - file.close() - if os.path.splitext(filename)[1] in [".py", ".pyc"]: - return filename - else: - return None - -def get_init(dirname): - """Get __init__ file path for module directory - - Parameters - ---------- - dirname : str - Find the __init__ file in directory `dirname` - - Returns - ------- - init_path : str - Path to __init__ file +def find_mod(module_name): """ - fbase = os.path.join(dirname, "__init__") - for ext in [".py", ".pyw"]: - fname = fbase + ext - if os.path.isfile(fname): - return fname + Find module `module_name` on sys.path, and return the path to module `module_name`. + - If `module_name` refers to a module directory, then return path to __init__ file. + - If `module_name` is a directory without an __init__file, return None. + - If module is missing or does not have a `.py` or `.pyw` extension, return None. + - Note that we are not interested in running bytecode. + - Otherwise, return the fill path of the module. -def find_mod(module_name): - """Find module `module_name` on sys.path - - Return the path to module `module_name`. If `module_name` refers to - a module directory then return path to __init__ file. Return full - path of module or None if module is missing or does not have .py or .pyw - extension. We are not interested in running bytecode. - Parameters ---------- module_name : str Returns ------- - modulepath : str - Path to module `module_name`. + module_path : str + Path to module `module_name`, its __init__.py, or None, + depending on above conditions. """ - parts = module_name.split(".") - basepath = find_module(parts[0]) - for submodname in parts[1:]: - basepath = find_module(submodname, [basepath]) - if basepath and os.path.isdir(basepath): - basepath = get_init(basepath) - return basepath + loader = importlib.util.find_spec(module_name) + module_path = loader.origin + if module_path is None: + return None + else: + split_path = module_path.split(".") + if split_path[1] in ["py", "pyw"]: + return module_path + else: + return None diff --git a/IPython/utils/tests/test_module_paths.py b/IPython/utils/tests/test_module_paths.py index 5b246471280..b315c694572 100644 --- a/IPython/utils/tests/test_module_paths.py +++ b/IPython/utils/tests/test_module_paths.py @@ -66,62 +66,42 @@ def teardown(): shutil.rmtree(TMP_TEST_DIR) sys.path = old_syspath - -def test_get_init_1(): - """See if get_init can find __init__.py in this testdir""" - with make_tempfile(join(TMP_TEST_DIR, "__init__.py")): - assert mp.get_init(TMP_TEST_DIR) - -def test_get_init_2(): - """See if get_init can find __init__.pyw in this testdir""" - with make_tempfile(join(TMP_TEST_DIR, "__init__.pyw")): - assert mp.get_init(TMP_TEST_DIR) - -def test_get_init_3(): - """get_init can't find __init__.pyc in this testdir""" - with make_tempfile(join(TMP_TEST_DIR, "__init__.pyc")): - nt.assert_is_none(mp.get_init(TMP_TEST_DIR)) - -def test_get_init_4(): - """get_init can't find __init__ in empty testdir""" - nt.assert_is_none(mp.get_init(TMP_TEST_DIR)) - - def test_find_mod_1(): + """ + Search for a directory's file path. + Expected output: a path to that directory's __init__.py file. + """ modpath = join(TMP_TEST_DIR, "xmod", "__init__.py") nt.assert_equal(mp.find_mod("xmod"), modpath) def test_find_mod_2(): + """ + Search for a directory's file path. + Expected output: a path to that directory's __init__.py file. + TODO: Confirm why this is a duplicate test. + """ modpath = join(TMP_TEST_DIR, "xmod", "__init__.py") nt.assert_equal(mp.find_mod("xmod"), modpath) def test_find_mod_3(): + """ + Search for a directory + a filename without its .py extension + Expected output: full path with .py extension. + """ modpath = join(TMP_TEST_DIR, "xmod", "sub.py") nt.assert_equal(mp.find_mod("xmod.sub"), modpath) def test_find_mod_4(): + """ + Search for a filename without its .py extension + Expected output: full path with .py extension + """ modpath = join(TMP_TEST_DIR, "pack.py") nt.assert_equal(mp.find_mod("pack"), modpath) def test_find_mod_5(): - modpath = join(TMP_TEST_DIR, "packpyc.pyc") - nt.assert_equal(mp.find_mod("packpyc"), modpath) - -def test_find_module_1(): - modpath = join(TMP_TEST_DIR, "xmod") - nt.assert_equal(mp.find_module("xmod"), modpath) - -def test_find_module_2(): - """Testing sys.path that is empty""" - nt.assert_is_none(mp.find_module("xmod", [])) - -def test_find_module_3(): - """Testing sys.path that is empty""" - nt.assert_is_none(mp.find_module(None, None)) - -def test_find_module_4(): - """Testing sys.path that is empty""" - nt.assert_is_none(mp.find_module(None)) - -def test_find_module_5(): - nt.assert_is_none(mp.find_module("xmod.nopack")) + """ + Search for a filename with a .pyc extension + Expected output: TODO: do we exclude or include .pyc files? + """ + nt.assert_equal(mp.find_mod("packpyc"), None) From 6f33fcd449312e0df728e9ec6ed4185b103e34f2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Mar 2017 18:42:14 -0800 Subject: [PATCH 0210/3726] Prototype async REPL using IPython, take III This is a squash and a rebase of a large number of commits from Min and I. For simplicity of managing it, history has been reduced to a single commit, but more historical versions can be found, in particular in PR 11155, or commit aedb5d6d3a441dcdb7180ac9b5cc03f91329117b to be more exact. --- IPython/core/async_helpers.py | 88 ++++++++ IPython/core/interactiveshell.py | 261 +++++++++++++++++++++-- IPython/core/magics/basic.py | 67 +++++- IPython/core/tests/test_async_helpers.py | 52 +++++ IPython/terminal/embed.py | 31 ++- IPython/terminal/tests/test_embed.py | 7 +- docs/source/conf.py | 3 +- docs/source/interactive/autoawait.rst | 186 ++++++++++++++++ docs/source/interactive/index.rst | 1 + docs/source/whatsnew/development.rst | 129 +++++++++++ docs/source/whatsnew/pr/await-repl.rst | 55 +++++ setup.py | 1 + 12 files changed, 847 insertions(+), 34 deletions(-) create mode 100644 IPython/core/async_helpers.py create mode 100644 IPython/core/tests/test_async_helpers.py create mode 100644 docs/source/interactive/autoawait.rst create mode 100644 docs/source/whatsnew/pr/await-repl.rst diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py new file mode 100644 index 00000000000..f22d93e2517 --- /dev/null +++ b/IPython/core/async_helpers.py @@ -0,0 +1,88 @@ +""" +Async helper function that are invalid syntax on Python 3.5 and below. + +Known limitation and possible improvement. + +Top level code that contain a return statement (instead of, or in addition to +await) will be detected as requiring being wrapped in async calls. This should +be prevented as early return will not work. +""" + + + +import ast +import sys +import inspect +from textwrap import dedent, indent +from types import CodeType + + +def _asyncio_runner(coro): + """ + Handler for asyncio autoawait + """ + import asyncio + return asyncio.get_event_loop().run_until_complete(coro) + + +def _curio_runner(coroutine): + """ + handler for curio autoawait + """ + import curio + return curio.run(coroutine) + + +if sys.version_info > (3, 5): + # nose refuses to avoid this file and async def is invalidsyntax + s = dedent(''' + def _trio_runner(function): + import trio + async def loc(coro): + """ + We need the dummy no-op async def to protect from + trio's internal. See https://github.com/python-trio/trio/issues/89 + """ + return await coro + return trio.run(loc, function) + ''') + exec(s, globals(), locals()) + + +def _asyncify(code: str) -> str: + """wrap code in async def definition. + + And setup a bit of context to run it later. + """ + res = dedent(""" + async def __wrapper__(): + try: + {usercode} + finally: + locals() + """).format(usercode=indent(code, ' ' * 8)[8:]) + return res + + +def _should_be_async(cell: str) -> bool: + """Detect if a block of code need to be wrapped in an `async def` + + Attempt to parse the block of code, it it compile we're fine. + Otherwise we wrap if and try to compile. + + If it works, assume it should be async. Otherwise Return False. + + Not handled yet: If the block of code has a return statement as the top + level, it will be seen as async. This is a know limitation. + """ + + try: + ast.parse(cell) + return False + except SyntaxError: + try: + ast.parse(_asyncify(cell)) + except SyntaxError: + return False + return True + return False diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 9d17bdf0846..b7540111a80 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -13,6 +13,7 @@ import abc import ast +import asyncio import atexit import builtins as builtin_mod import functools @@ -30,6 +31,7 @@ from pickleshare import PickleShareDB from traitlets.config.configurable import SingletonConfigurable +from traitlets.utils.importstring import import_item from IPython.core import oinspect from IPython.core import magic from IPython.core import page @@ -73,7 +75,7 @@ from IPython.utils.tempdir import TemporaryDirectory from traitlets import ( Integer, Bool, CaselessStrEnum, Enum, List, Dict, Unicode, Instance, Type, - observe, default, + observe, default, validate, Any ) from warnings import warn from logging import error @@ -113,6 +115,102 @@ class ProvisionalWarning(DeprecationWarning): _single_targets_nodes = (ast.AugAssign, ) #----------------------------------------------------------------------------- +# Await Helpers +#----------------------------------------------------------------------------- + +def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: + """Return a function that do not create a new local scope. + + Given a function, create a clone of this function where the co_newlocal flag + has been removed, making this function code actually run in the sourounding + scope. + + We need this in order to run asynchronous code in user level namespace. + """ + from types import CodeType, FunctionType + CO_NEWLOCALS = 0x0002 + code = function.__code__ + new_code = CodeType( + code.co_argcount, + code.co_kwonlyargcount, + code.co_nlocals, + code.co_stacksize, + code.co_flags & ~CO_NEWLOCALS, + code.co_code, + code.co_consts, + code.co_names, + code.co_varnames, + code.co_filename, + code.co_name, + code.co_firstlineno, + code.co_lnotab, + code.co_freevars, + code.co_cellvars + ) + return FunctionType(new_code, globals(), function.__name__, function.__defaults__) + + +if sys.version_info > (3,5): + from .async_helpers import (_asyncio_runner, _curio_runner, _trio_runner, + _should_be_async, _asyncify + ) +else : + _asyncio_runner = _curio_runner = _trio_runner = None + + def _should_be_async(whatever:str)->bool: + return False + + +def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module: + """ + Parse a cell with top-level await and modify the AST to be able to run it later. + + Parameter + --------- + + cell: str + The code cell to asyncronify + wrapper_name: str + The name of the function to be used to wrap the passed `cell`. It is + advised to **not** use a python identifier in order to not pollute the + global namespace in which the function will be ran. + + Return + ------ + + A module object AST containing **one** function named `wrapper_name`. + + The given code is wrapped in a async-def function, parsed into an AST, and + the resulting function definition AST is modified to return the last + expression. + + The last expression or await node is moved into a return statement at the + end of the function, and removed from its original location. If the last + node is not Expr or Await nothing is done. + + The function `__code__` will need to be later modified (by + ``removed_co_newlocals``) in a subsequent step to not create new `locals()` + meaning that the local and global scope are the same, ie as if the body of + the function was at module level. + + Lastly a call to `locals()` is made just before the last expression of the + function, or just after the last assignment or statement to make sure the + global dict is updated as python function work with a local fast cache which + is updated only on `local()` calls. + """ + + from ast import Expr, Await, Return + tree = ast.parse(_asyncify(cell)) + + function_def = tree.body[0] + function_def.name = wrapper_name + try_block = function_def.body[0] + lastexpr = try_block.body[-1] + if isinstance(lastexpr, (Expr, Await)): + try_block.body[-1] = Return(lastexpr.value) + ast.fix_missing_locations(tree) + return tree +#----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -258,6 +356,40 @@ class InteractiveShell(SingletonConfigurable): """ ).tag(config=True) + autoawait = Bool(True, help= + """ + Automatically run await statement in the top level repl. + """ + ).tag(config=True) + + loop_runner_map ={ + 'asyncio':_asyncio_runner, + 'curio':_curio_runner, + 'trio':_trio_runner, + } + + loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner", + allow_none=True, + help="""Select the loop runner that will be used to execute top-level asynchronous code""" + ).tag(config=True) + + @default('loop_runner') + def _default_loop_runner(self): + return import_item("IPython.core.interactiveshell._asyncio_runner") + + @validate('loop_runner') + def _import_runner(self, proposal): + if isinstance(proposal.value, str): + if proposal.value in self.loop_runner_map: + return self.loop_runner_map[proposal.value] + runner = import_item(proposal.value) + if not callable(runner): + raise ValueError('loop_runner must be callable') + return runner + if not callable(proposal.value): + raise ValueError('loop_runner must be callable') + return proposal.value + automagic = Bool(True, help= """ Enable magic commands to be called without the leading %. @@ -1449,6 +1581,7 @@ def _ofind(self, oname, namespaces=None): parent = None obj = None + # Look for the given name by splitting it in parts. If the head is # found, then we look for all the remaining parts as members, and only # declare success if we can find them all. @@ -1984,7 +2117,6 @@ def init_completer(self): self.set_hook('complete_command', cd_completer, str_key = '%cd') self.set_hook('complete_command', reset_completer, str_key = '%reset') - @skip_doctest def complete(self, text, line=None, cursor_pos=None): """Return the completed text and a list of completions. @@ -2667,14 +2799,36 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr return result def _run_cell(self, raw_cell, store_history, silent, shell_futures): - """Internal method to run a complete IPython cell. + """Internal method to run a complete IPython cell.""" + return self.loop_runner( + self.run_cell_async( + raw_cell, + store_history=store_history, + silent=silent, + shell_futures=shell_futures, + ) + ) + + @asyncio.coroutine + def run_cell_async(self, raw_cell, store_history=False, silent=False, shell_futures=True): + """Run a complete IPython cell asynchronously. Parameters ---------- raw_cell : str + The code (including IPython code such as %magic functions) to run. store_history : bool + If True, the raw and translated cell will be stored in IPython's + history. For user code calling back into IPython's machinery, this + should be set to False. silent : bool + If True, avoid side-effects, such as implicit displayhooks and + and logging. silent=True forces store_history=False. shell_futures : bool + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. Returns ------- @@ -2749,13 +2903,33 @@ def error_before_exec(value): # compiler compiler = self.compile if shell_futures else CachingCompiler() + _run_async = False + with self.builtin_trap: cell_name = self.compile.cache(cell, self.execution_count) with self.display_trap: # Compile to bytecode try: - code_ast = compiler.ast_parse(cell, filename=cell_name) + if self.autoawait and _should_be_async(cell): + # the code AST below will not be user code: we wrap it + # in an `async def`. This will likely make some AST + # transformer below miss some transform opportunity and + # introduce a small coupling to run_code (in which we + # bake some assumptions of what _ast_asyncify returns. + # they are ways around (like grafting part of the ast + # later: + # - Here, return code_ast.body[0].body[1:-1], as well + # as last expression in return statement which is + # the user code part. + # - Let it go through the AST transformers, and graft + # - it back after the AST transform + # But that seem unreasonable, at least while we + # do not need it. + code_ast = _ast_asyncify(cell, 'async-def-wrapper') + _run_async = True + else: + code_ast = compiler.ast_parse(cell, filename=cell_name) except self.custom_exceptions as e: etype, value, tb = sys.exc_info() self.CustomTB(etype, value, tb) @@ -2780,9 +2954,11 @@ def error_before_exec(value): self.displayhook.exec_result = result # Execute the user code - interactivity = 'none' if silent else self.ast_node_interactivity - has_raised = self.run_ast_nodes(code_ast.body, cell_name, - interactivity=interactivity, compiler=compiler, result=result) + interactivity = "none" if silent else self.ast_node_interactivity + if _run_async: + interactivity = 'async' + has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name, + interactivity=interactivity, compiler=compiler, result=result) self.last_execution_succeeded = not has_raised self.last_execution_result = result @@ -2826,12 +3002,12 @@ def transform_ast(self, node): except Exception: warn("AST transformer %r threw an error. It will be unregistered." % transformer) self.ast_transformers.remove(transformer) - + if self.ast_transformers: ast.fix_missing_locations(node) return node - + @asyncio.coroutine def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr', compiler=compile, result=None): """Run a sequence of AST nodes. The execution mode depends on the @@ -2852,6 +3028,12 @@ def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='la are not displayed) 'last_expr_or_assign' will run the last expression or the last assignment. Other values for this parameter will raise a ValueError. + + Experimental value: 'async' Will try to run top level interactive + async/await code in default runner, this will not respect the + interactivty setting and will only run the last node if it is an + expression. + compiler : callable A function with the same interface as the built-in compile(), to turn the AST nodes into code objects. Default is the built-in compile(). @@ -2880,6 +3062,7 @@ def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='la nodelist.append(nnode) interactivity = 'last_expr' + _async = False if interactivity == 'last_expr': if isinstance(nodelist[-1], ast.Expr): interactivity = "last" @@ -2892,20 +3075,32 @@ def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='la to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:] elif interactivity == 'all': to_run_exec, to_run_interactive = [], nodelist + elif interactivity == 'async': + _async = True else: raise ValueError("Interactivity was %r" % interactivity) try: - for i, node in enumerate(to_run_exec): - mod = ast.Module([node]) - code = compiler(mod, cell_name, "exec") - if self.run_code(code, result): - return True - - for i, node in enumerate(to_run_interactive): - mod = ast.Interactive([node]) - code = compiler(mod, cell_name, "single") - if self.run_code(code, result): + if _async: + # If interactivity is async the semantics of run_code are + # completely different Skip usual machinery. + mod = ast.Module(nodelist) + async_wrapper_code = compiler(mod, 'cell_name', 'exec') + exec(async_wrapper_code, self.user_global_ns, self.user_ns) + async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__ + if (yield from self.run_code(async_code, result, async_=True)): return True + else: + for i, node in enumerate(to_run_exec): + mod = ast.Module([node]) + code = compiler(mod, cell_name, "exec") + if (yield from self.run_code(code, result)): + return True + + for i, node in enumerate(to_run_interactive): + mod = ast.Interactive([node]) + code = compiler(mod, cell_name, "single") + if (yield from self.run_code(code, result)): + return True # Flush softspace if softspace(sys.stdout, 0): @@ -2928,7 +3123,23 @@ def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='la return False - def run_code(self, code_obj, result=None): + def _async_exec(self, code_obj: types.CodeType, user_ns: dict): + """ + Evaluate an asynchronous code object using a code runner + + Fake asynchronous execution of code_object in a namespace via a proxy namespace. + + Returns coroutine object, which can be executed via async loop runner + + WARNING: The semantics of `async_exec` are quite different from `exec`, + in particular you can only pass a single namespace. It also return a + handle to the value of the last things returned by code_object. + """ + + return eval(code_obj, user_ns) + + @asyncio.coroutine + def run_code(self, code_obj, result=None, *, async_=False): """Execute a code object. When an exception occurs, self.showtraceback() is called to display a @@ -2940,6 +3151,8 @@ def run_code(self, code_obj, result=None): A compiled code object, to be executed result : ExecutionResult, optional An object to store exceptions that occur during execution. + async_ : Bool (Experimental) + Attempt to run top-level asynchronous code in a default loop. Returns ------- @@ -2957,8 +3170,12 @@ def run_code(self, code_obj, result=None): try: try: self.hooks.pre_run_code_hook() - #rprint('Running code', repr(code_obj)) # dbg - exec(code_obj, self.user_global_ns, self.user_ns) + if async_: + last_expr = (yield from self._async_exec(code_obj, self.user_ns)) + code = compile('last_expr', 'fake', "single") + exec(code, {'last_expr': last_expr}) + else: + exec(code_obj, self.user_global_ns, self.user_ns) finally: # Reset our crash handler in place sys.excepthook = old_excepthook diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 87532e13ff8..29434e9cfa2 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -2,19 +2,20 @@ import argparse -import textwrap +from logging import error import io -import sys from pprint import pformat +import textwrap +import sys +from warnings import warn +from traitlets.utils.importstring import import_item from IPython.core import magic_arguments, page from IPython.core.error import UsageError from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes from IPython.utils.text import format_screen, dedent, indent from IPython.testing.skipdoctest import skip_doctest from IPython.utils.ipstruct import Struct -from warnings import warn -from logging import error class MagicsDisplay(object): @@ -378,6 +379,64 @@ def xmode_switch_err(name): except: xmode_switch_err('user') + @line_magic + def autoawait(self, parameter_s): + """ + Allow to change the status of the autoawait option. + + This allow you to set a specific asynchronous code runner. + + If no value is passed, print the currently used asynchronous integration + and whether it is activated. + + It can take a number of value evaluated in the following order: + + - False/false/off deactivate autoawait integration + - True/true/on activate autoawait integration using configured default + loop + - asyncio/curio/trio activate autoawait integration and use integration + with said library. + + If the passed parameter does not match any of the above and is a python + identifier, get said object from user namespace and set it as the + runner, and activate autoawait. + + If the object is a fully qualified object name, attempt to import it and + set it as the runner, and activate autoawait.""" + + param = parameter_s.strip() + d = {True: "on", False: "off"} + + if not param: + print("IPython autoawait is `{}`, and set to use `{}`".format( + d[self.shell.autoawait], + self.shell.loop_runner + )) + return None + + if param.lower() in ('false', 'off'): + self.shell.autoawait = False + return None + if param.lower() in ('true', 'on'): + self.shell.autoawait = True + return None + + if param in self.shell.loop_runner_map: + self.shell.loop_runner = param + self.shell.autoawait = True + return None + + if param in self.shell.user_ns : + self.shell.loop_runner = self.shell.user_ns[param] + self.shell.autoawait = True + return None + + runner = import_item(param) + + self.shell.loop_runner = runner + self.shell.autoawait = True + + @line_magic def pip(self, args=''): """ diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py new file mode 100644 index 00000000000..6c542085eed --- /dev/null +++ b/IPython/core/tests/test_async_helpers.py @@ -0,0 +1,52 @@ +""" +Test for async helpers. + +Should only trigger on python 3.5+ or will have syntax errors. +""" + +import sys +import nose.tools as nt +from textwrap import dedent +from unittest import TestCase + +ip = get_ipython() +iprc = lambda x: ip.run_cell(dedent(x)) + +if sys.version_info > (3,5): + from IPython.core.async_helpers import _should_be_async + + class AsyncTest(TestCase): + + def test_should_be_async(self): + nt.assert_false(_should_be_async("False")) + nt.assert_true(_should_be_async("await bar()")) + nt.assert_true(_should_be_async("x = await bar()")) + nt.assert_false(_should_be_async(dedent(""" + async def awaitable(): + pass + """))) + + def test_execute(self): + iprc(""" + import asyncio + await asyncio.sleep(0.001) + """) + + def test_autoawait(self): + ip.run_cell('%autoawait False') + ip.run_cell('%autoawait True') + iprc(''' + from asyncio import sleep + await.sleep(0.1) + ''') + + def test_autoawait_curio(self): + ip.run_cell('%autoawait curio') + + def test_autoawait_trio(self): + ip.run_cell('%autoawait trio') + + def tearDown(self): + ip.loop_runner = 'asyncio' + + diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index ea5c4f15b8e..fa0345c3245 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -19,6 +19,23 @@ from traitlets import Bool, CBool, Unicode from IPython.utils.io import ask_yes_no +from contextlib import contextmanager + +_sentinel = object() +@contextmanager +def new_context(): + import trio._core._run as tcr + old_runner = getattr(tcr.GLOBAL_RUN_CONTEXT, 'runner', _sentinel) + old_task = getattr(tcr.GLOBAL_RUN_CONTEXT, 'task', None) + if old_runner is not _sentinel: + del tcr.GLOBAL_RUN_CONTEXT.runner + tcr.GLOBAL_RUN_CONTEXT.task = None + yield + if old_runner is not _sentinel: + tcr.GLOBAL_RUN_CONTEXT.runner = old_runner + tcr.GLOBAL_RUN_CONTEXT.task = old_task + + class KillEmbedded(Exception):pass # kept for backward compatibility as IPython 6 was released with @@ -366,6 +383,9 @@ def embed(**kwargs): config = load_default_config() config.InteractiveShellEmbed = config.TerminalInteractiveShell kwargs['config'] = config + using = kwargs.get('using', 'trio') + if using : + kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor'}}) #save ps1/ps2 if defined ps1 = None ps2 = None @@ -380,11 +400,12 @@ def embed(**kwargs): cls = type(saved_shell_instance) cls.clear_instance() frame = sys._getframe(1) - shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( - frame.f_code.co_filename, frame.f_lineno), **kwargs) - shell(header=header, stack_depth=2, compile_flags=compile_flags, - _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) - InteractiveShellEmbed.clear_instance() + with new_context(): + shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( + frame.f_code.co_filename, frame.f_lineno), **kwargs) + shell(header=header, stack_depth=2, compile_flags=compile_flags, + _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) + InteractiveShellEmbed.clear_instance() #restore previous instance if saved_shell_instance is not None: cls = type(saved_shell_instance) diff --git a/IPython/terminal/tests/test_embed.py b/IPython/terminal/tests/test_embed.py index 5d75ad0d887..de5b1e34864 100644 --- a/IPython/terminal/tests/test_embed.py +++ b/IPython/terminal/tests/test_embed.py @@ -72,6 +72,7 @@ def test_nest_embed(): child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor'], env=env) + child.timeout = 5 child.expect(ipy_prompt) child.sendline("import IPython") child.expect(ipy_prompt) @@ -86,7 +87,8 @@ def test_nest_embed(): except pexpect.TIMEOUT as e: print(e) #child.interact() - child.sendline("embed1 = get_ipython()"); child.expect(ipy_prompt) + child.sendline("embed1 = get_ipython()") + child.expect(ipy_prompt) child.sendline("print('true' if embed1 is not ip0 else 'false')") assert(child.expect(['true\r\n', 'false\r\n']) == 0) child.expect(ipy_prompt) @@ -103,7 +105,8 @@ def test_nest_embed(): except pexpect.TIMEOUT as e: print(e) #child.interact() - child.sendline("embed2 = get_ipython()"); child.expect(ipy_prompt) + child.sendline("embed2 = get_ipython()") + child.expect(ipy_prompt) child.sendline("print('true' if embed2 is not embed1 else 'false')") assert(child.expect(['true\r\n', 'false\r\n']) == 0) child.expect(ipy_prompt) diff --git a/docs/source/conf.py b/docs/source/conf.py index 20ef8f9a5ac..7d99ebd36ae 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,7 +143,8 @@ def is_stable(extra): # Exclude these glob-style patterns when looking for source files. They are # relative to the source/ directory. -exclude_patterns = ['whatsnew/pr'] +exclude_patterns = ['whatsnew/pr/antigravity-feature.*', + 'whatsnew/pr/incompat-switching-to-perl.*'] # If true, '()' will be appended to :func: etc. cross-reference text. diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst new file mode 100644 index 00000000000..3d84e49e958 --- /dev/null +++ b/docs/source/interactive/autoawait.rst @@ -0,0 +1,186 @@ + +.. autoawait: + +Asynchronous in REPL: Autoawait +=============================== + +Starting with IPython 6.0, and when user Python 3.6 and above, IPython offer the +ability to run asynchronous code from the REPL. constructs which are +:exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. + +When a supported libray is used, IPython will automatically `await` Futures +and Coroutines in the REPL. This will happen if an :ref:`await ` (or `async`) is +use at top level scope, or if any structure valid only in `async def +`_ function +context are present. For example, the following being a syntax error in the +Python REPL:: + + Python 3.6.0 + [GCC 4.2.1] + Type "help", "copyright", "credits" or "license" for more information. + >>> import aiohttp + >>> result = aiohttp.get('https://api.github.com') + >>> response = await result + File "", line 1 + response = await result + ^ + SyntaxError: invalid syntax + +Should behave as expected in the IPython REPL:: + + Python 3.6.0 + Type 'copyright', 'credits' or 'license' for more information + IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help. + + In [1]: import aiohttp + ...: result = aiohttp.get('https://api.github.com') + + In [2]: response = await result + + + In [3]: await response.json() + Out[3]: + {'authorizations_url': 'https://api.github.com/authorizations', + 'code_search_url': 'https://api.github.com/search/code?q={query}...', + ... + } + + +You can use the ``c.InteractiveShell.autoawait`` configuration option and set it +to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also +use the :magic:`%autoawait` magic to toggle the behavior at runtime:: + + In [1]: %autoawait False + + In [2]: %autoawait + IPython autoawait is `Off`, and set to use `IPython.core.interactiveshell._asyncio_runner` + + + +By default IPython will assume integration with Python's provided +:mod:`asyncio`, but integration with other libraries is provided. In particular +we provide experimental integration with the ``curio`` and ``trio`` library. + +You can switch current integration by using the +``c.InteractiveShell.loop_runner`` option or the ``autoawait `` magic. + +For example:: + + In [1]: %autoawait trio + + In [2]: import trio + + In [3]: async def child(i): + ...: print(" child %s goes to sleep"%i) + ...: await trio.sleep(2) + ...: print(" child %s wakes up"%i) + + In [4]: print('parent start') + ...: async with trio.open_nursery() as n: + ...: for i in range(5): + ...: n.spawn(child, i) + ...: print('parent end') + parent start + child 2 goes to sleep + child 0 goes to sleep + child 3 goes to sleep + child 1 goes to sleep + child 4 goes to sleep + + child 2 wakes up + child 1 wakes up + child 0 wakes up + child 3 wakes up + child 4 wakes up + parent end + + +In the above example, ``async with`` at top level scope is a syntax error in +Python. + +Using this mode can have unexpected consequences if used in interaction with +other features of IPython and various registered extensions. In particular if you +are a direct or indirect user of the AST transformers, these may not apply to +your code. + +The default loop, or runner does not run in the background, so top level +asynchronous code must finish for the REPL to allow you to enter more code. As +with usual Python semantic, the awaitables are started only when awaited for the +first time. That is to say, in first example, no network request is done between +``In[1]`` and ``In[2]``. + + +Internals +========= + +As running asynchronous code is not supported in interactive REPL as of Python +3.6 we have to rely to a number of complex workaround to allow this to happen. +It is interesting to understand how this works in order to understand potential +bugs, or provide a custom runner. + +Among the many approaches that are at our disposition, we find only one that +suited out need. Under the hood we :ct the code object from a async-def function +and run it in global namesapace after modifying the ``__code__`` object.:: + + async def inner_async(): + locals().update(**global_namespace) + # + # here is user code + # + return last_user_statement + codeobj = modify(inner_async.__code__) + coroutine = eval(codeobj, user_ns) + display(loop_runner(coroutine)) + + + +The first thing you'll notice is that unlike classical ``exec``, there is only +one name space. Second, user code runs in a function scope, and not a module +scope. + +On top of the above there are significant modification to the AST of +``function``, and ``loop_runner`` can be arbitrary complex. So there is a +significant overhead to this kind of code. + +By default the generated coroutine function will be consumed by Asyncio's +``loop_runner = asyncio.get_evenloop().run_until_complete()`` method. It is +though possible to provide your own. + +A loop runner is a *synchronous* function responsible from running a coroutine +object. + +The runner is responsible from ensuring that ``coroutine`` run to completion, +and should return the result of executing the coroutine. Let's write a +runner for ``trio`` that print a message when used as an exercise, ``trio`` is +special as it usually prefer to run a function object and make a coroutine by +itself, we can get around this limitation by wrapping it in an async-def without +parameters and passing this value to ``trio``:: + + + In [1]: import trio + ...: from types import CoroutineType + ...: + ...: def trio_runner(coro:CoroutineType): + ...: print('running asynchronous code') + ...: async def corowrap(coro): + ...: return await coro + ...: return trio.run(corowrap, coro) + +We can set it up by passing it to ``%autoawait``:: + + In [2]: %autoawait trio_runner + + In [3]: async def async_hello(name): + ...: await trio.sleep(1) + ...: print(f'Hello {name} world !') + ...: await trio.sleep(1) + + In [4]: await async_hello('async') + running asynchronous code + Hello async world ! + + +Asynchronous programming in python (and in particular in the REPL) is still a +relatively young subject. Feel free to contribute improvements to this codebase +and give us feedback. diff --git a/docs/source/interactive/index.rst b/docs/source/interactive/index.rst index 97332e1839a..7010c51b320 100644 --- a/docs/source/interactive/index.rst +++ b/docs/source/interactive/index.rst @@ -21,6 +21,7 @@ done some work in the classic Python REPL. plotting reference shell + autoawait tips python-ipython-diff magics diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 13f02a44af3..4a62f47a40f 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -11,6 +11,135 @@ This document describes in-flight development work. `docs/source/whatsnew/pr` folder +Released .... ...., 2017 + + +Need to be updated: + +.. toctree:: + :maxdepth: 2 + :glob: + + pr/* + +IPython 6 feature a major improvement in the completion machinery which is now +capable of completing non-executed code. It is also the first version of IPython +to stop compatibility with Python 2, which is still supported on the bugfix only +5.x branch. Read below to have a non-exhaustive list of new features. + +Make sure you have pip > 9.0 before upgrading. +You should be able to update by using: + +.. code:: + + pip install ipython --upgrade + +New completion API and Interface +-------------------------------- + +The completer Completion API has seen an overhaul, and the new completer have +plenty of improvement both from the end users of terminal IPython or for +consumers of the API. + +This new API is capable of pulling completions from :any:`jedi`, thus allowing +type inference on non-executed code. If :any:`jedi` is installed completion like +the following are now becoming possible without code evaluation: + + >>> data = ['Number of users', 123_456] + ... data[0]. + +That is to say, IPython is now capable of inferring that `data[0]` is a string, +and will suggest completions like `.capitalize`. The completion power of IPython +will increase with new Jedi releases, and a number of bugs and more completions +are already available on development version of :any:`jedi` if you are curious. + +With the help of prompt toolkit, types of completions can be shown in the +completer interface: + +.. image:: ../_images/jedi_type_inference_60.png + :alt: Jedi showing ability to do type inference + :align: center + :width: 400px + :target: ../_images/jedi_type_inference_60.png + +The appearance of the completer is controlled by the +``c.TerminalInteractiveShell.display_completions`` option that will show the +type differently depending on the value among ``'column'``, ``'multicolumn'`` +and ``'readlinelike'`` + +The use of Jedi also full fill a number of request and fix a number of bugs +like case insensitive completion, completion after division operator: See +:ghpull:`10182`. + +Extra patches and updates will be needed to the :mod:`ipykernel` package for +this feature to be available to other clients like jupyter Notebook, Lab, +Nteract, Hydrogen... + +The use of Jedi can is barely noticeable on recent enough machines, but can be +feel on older ones, in cases were Jedi behavior need to be adjusted, the amount +of time given to Jedi to compute type inference can be adjusted with +``c.IPCompleter.jedi_compute_type_timeout``, with object whose type were not +inferred will be shown as ````. Jedi can also be completely deactivated +by using the ``c.Completer.use_jedi=False`` option. + + +The old ``Completer.complete()`` API is waiting deprecation and should be +replaced replaced by ``Completer.completions()`` in a near future. Feedback on +the current state of the API and suggestions welcome. + +Python 3 only codebase +---------------------- + +One of the large challenges in IPython 6.0 has been the adoption of a pure +Python 3 code base, which lead us to great length to upstream patches in pip, +pypi and warehouse to make sure Python 2 system still upgrade to the latest +compatible Python version compatible. + +We remind our Python 2 users that IPython 5 is still compatible with Python 2.7, +still maintained and get regular releases. Using pip 9+, upgrading IPython will +automatically upgrade to the latest version compatible with your system. + +.. warning:: + + If you are on a system using an older verison of pip on Python 2, pip may + still install IPython 6.0 on your system, and IPython will refuse to start. + You can fix this by ugrading pip, and reinstalling ipython, or forcing pip to + install an earlier version: ``pip install 'ipython<6'`` + +The ability to use only Python 3 on the code base of IPython has bring a number +of advantage. Most of the newly written code make use of `optional function type +anotation `_ leading to clearer code +and better documentation. + +The total size of the repository has also for a first time between releases +(excluding the big split for 4.0) decreased by about 1500 lines, potentially +quite a bit more codewide as some documents like this one are append only and +are about 300 lines long. + +The removal as of Python2/Python3 shim layer has made the code quite clearer and +more idiomatic in a number of location, and much friendlier to work with and +understand. We hope to further embrace Python 3 capability in the next release +cycle and introduce more of the Python 3 only idioms (yield from, kwarg only, +general unpacking) in the code base of IPython, and see if we can take advantage +of these as well to improve user experience with better error messages and +hints. + + +Miscs improvements +------------------ + + +- The :cellmagic:`capture` magic can now capture the result of a cell (from an + expression on the last line), as well as printed and displayed output. + :ghpull:`9851`. + +- Pressing Ctrl-Z in the terminal debugger now suspends IPython, as it already + does in the main terminal prompt. + +- autoreload can now reload ``Enum``. See :ghissue:`10232` and :ghpull:`10316` + +- IPython.display has gained a :any:`GeoJSON ` object. + :ghpull:`10288` and :ghpull:`10253` .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. diff --git a/docs/source/whatsnew/pr/await-repl.rst b/docs/source/whatsnew/pr/await-repl.rst new file mode 100644 index 00000000000..614a00a5a4c --- /dev/null +++ b/docs/source/whatsnew/pr/await-repl.rst @@ -0,0 +1,55 @@ +Await REPL +---------- + +:ghpull:`10390` introduced the ability to ``await`` Futures and +Coroutines in the REPL. For example:: + + Python 3.6.0 + Type 'copyright', 'credits' or 'license' for more information + IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help. + + In [1]: import aiohttp + ...: result = aiohttp.get('https://api.github.com') + + In [2]: response = await result + + + In [3]: await response.json() + Out[3]: + {'authorizations_url': 'https://api.github.com/authorizations', + 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', + ... + } + + +Integration is by default with `asyncio`, but other libraries can be configured, +like ``curio`` or ``trio``, to improve concurrency in the REPL:: + + In [1]: %autoawait trio + + In [2]: import trio + + In [3]: async def child(i): + ...: print(" child %s goes to sleep"%i) + ...: await trio.sleep(2) + ...: print(" child %s wakes up"%i) + + In [4]: print('parent start') + ...: async with trio.open_nursery() as n: + ...: for i in range(3): + ...: n.spawn(child, i) + ...: print('parent end') + parent start + child 2 goes to sleep + child 0 goes to sleep + child 1 goes to sleep + + child 2 wakes up + child 1 wakes up + child 0 wakes up + parent end + +See :ref:`autoawait` for more information. + + + diff --git a/setup.py b/setup.py index bd720883057..ed188415d92 100755 --- a/setup.py +++ b/setup.py @@ -201,6 +201,7 @@ extras_require.update({ ':python_version == "3.4"': ['typing'], + ':python_version >= "3.5"': ['trio', 'curio'], ':sys_platform != "win32"': ['pexpect'], ':sys_platform == "darwin"': ['appnope'], ':sys_platform == "win32"': ['colorama'], From 9f216675ddbdf3f5585800b2c183c6f6f1202b3e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Aug 2018 19:21:03 -0700 Subject: [PATCH 0211/3726] fix UnboundLocalError --- IPython/core/interactiveshell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index b7540111a80..97ade0dec9d 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2789,6 +2789,7 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr ------- result : :class:`ExecutionResult` """ + result = None try: result = self._run_cell( raw_cell, store_history, silent, shell_futures) From c5b1e0581c159b89a9f07d6510204a7e617548df Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Aug 2018 21:02:15 -0700 Subject: [PATCH 0212/3726] titleto have doc build --- docs/source/whatsnew/pr/deprecations.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/whatsnew/pr/deprecations.rst b/docs/source/whatsnew/pr/deprecations.rst index 386ab114e36..00fa5f9812a 100644 --- a/docs/source/whatsnew/pr/deprecations.rst +++ b/docs/source/whatsnew/pr/deprecations.rst @@ -1,3 +1,6 @@ +Depreations +=========== + A couple of unused function and methods have been deprecated and will be removed in future versions: From 05789e8543f04afe240a7238b15a2ec4343f0bfc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 14 Aug 2018 10:48:26 -0700 Subject: [PATCH 0213/3726] fix docs --- docs/source/interactive/autoawait.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 3d84e49e958..35bd4e787a9 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -1,5 +1,4 @@ - -.. autoawait: +.. _autoawait: Asynchronous in REPL: Autoawait =============================== From 4fac0cbb6a9567021351b51bc0b861d8eb7e9f91 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 14 Aug 2018 11:22:41 -0700 Subject: [PATCH 0214/3726] Load the asycn ext only on 3.5+ --- IPython/core/interactiveshell.py | 14 ++-- IPython/core/magics/__init__.py | 2 +- IPython/core/magics/basic.py | 116 ++++++++++++++++--------------- 3 files changed, 70 insertions(+), 62 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 97ade0dec9d..e3f146ac265 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -150,14 +150,16 @@ def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: return FunctionType(new_code, globals(), function.__name__, function.__defaults__) +# we still need to run things using the asyncio eventloop, but there is no +# async integration +from .async_helpers import (_asyncio_runner, _asyncify) + if sys.version_info > (3,5): - from .async_helpers import (_asyncio_runner, _curio_runner, _trio_runner, - _should_be_async, _asyncify - ) + from .async_helpers import _curio_runner, _trio_runner, _should_be_async else : - _asyncio_runner = _curio_runner = _trio_runner = None + _curio_runner = _trio_runner = None - def _should_be_async(whatever:str)->bool: + def _should_be_async(cell:str)->bool: return False @@ -2200,6 +2202,8 @@ def init_magics(self): m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics, m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics, ) + if sys.version_info >(3,5): + self.register_magics(m.AsyncMagics) # Register Magic Aliases mman = self.magics_manager diff --git a/IPython/core/magics/__init__.py b/IPython/core/magics/__init__.py index d2fd5a6cfb6..841f4da2869 100644 --- a/IPython/core/magics/__init__.py +++ b/IPython/core/magics/__init__.py @@ -14,7 +14,7 @@ from ..magic import Magics, magics_class from .auto import AutoMagics -from .basic import BasicMagics +from .basic import BasicMagics, AsyncMagics from .code import CodeMagics, MacroToEdit from .config import ConfigMagics from .display import DisplayMagics diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 29434e9cfa2..c60a1934b9f 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -379,62 +379,6 @@ def xmode_switch_err(name): except: xmode_switch_err('user') - @line_magic - def autoawait(self, parameter_s): - """ - Allow to change the status of the autoawait option. - - This allow you to set a specific asynchronous code runner. - - If no value is passed, print the currently used asynchronous integration - and whether it is activated. - - It can take a number of value evaluated in the following order: - - - False/false/off deactivate autoawait integration - - True/true/on activate autoawait integration using configured default - loop - - asyncio/curio/trio activate autoawait integration and use integration - with said library. - - If the passed parameter does not match any of the above and is a python - identifier, get said object from user namespace and set it as the - runner, and activate autoawait. - - If the object is a fully qualified object name, attempt to import it and - set it as the runner, and activate autoawait.""" - - param = parameter_s.strip() - d = {True: "on", False: "off"} - - if not param: - print("IPython autoawait is `{}`, and set to use `{}`".format( - d[self.shell.autoawait], - self.shell.loop_runner - )) - return None - - if param.lower() in ('false', 'off'): - self.shell.autoawait = False - return None - if param.lower() in ('true', 'on'): - self.shell.autoawait = True - return None - - if param in self.shell.loop_runner_map: - self.shell.loop_runner = param - self.shell.autoawait = True - return None - - if param in self.shell.user_ns : - self.shell.loop_runner = self.shell.user_ns[param] - self.shell.autoawait = True - return None - - runner = import_item(param) - - self.shell.loop_runner = runner - self.shell.autoawait = True @line_magic @@ -656,3 +600,63 @@ def notebook(self, s): nb = v4.new_notebook(cells=cells) with io.open(args.filename, 'w', encoding='utf-8') as f: write(nb, f, version=4) + +@magics_class +class AsyncMagics(BasicMagics): + + @line_magic + def autoawait(self, parameter_s): + """ + Allow to change the status of the autoawait option. + + This allow you to set a specific asynchronous code runner. + + If no value is passed, print the currently used asynchronous integration + and whether it is activated. + + It can take a number of value evaluated in the following order: + + - False/false/off deactivate autoawait integration + - True/true/on activate autoawait integration using configured default + loop + - asyncio/curio/trio activate autoawait integration and use integration + with said library. + + If the passed parameter does not match any of the above and is a python + identifier, get said object from user namespace and set it as the + runner, and activate autoawait. + + If the object is a fully qualified object name, attempt to import it and + set it as the runner, and activate autoawait.""" + + param = parameter_s.strip() + d = {True: "on", False: "off"} + + if not param: + print("IPython autoawait is `{}`, and set to use `{}`".format( + d[self.shell.autoawait], + self.shell.loop_runner + )) + return None + + if param.lower() in ('false', 'off'): + self.shell.autoawait = False + return None + if param.lower() in ('true', 'on'): + self.shell.autoawait = True + return None + + if param in self.shell.loop_runner_map: + self.shell.loop_runner = param + self.shell.autoawait = True + return None + + if param in self.shell.user_ns : + self.shell.loop_runner = self.shell.user_ns[param] + self.shell.autoawait = True + return None + + runner = import_item(param) + + self.shell.loop_runner = runner + self.shell.autoawait = True From 5ea200eb7867976b3cdc60e2a378eb83938a3fc9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 14 Aug 2018 11:30:22 -0700 Subject: [PATCH 0215/3726] fix runnign on 3.7 --- IPython/core/async_helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index f22d93e2517..02bc2b52e5e 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -77,7 +77,9 @@ def _should_be_async(cell: str) -> bool: """ try: - ast.parse(cell) + # we can't limit ourself to ast.parse, as it __accepts__ to parse on + # 3.7+, but just does not _compile_ + compile(cell, '<>', 'exec') return False except SyntaxError: try: From ac931b8c787704b969247ae0ca4d634be8a727c4 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 16 Aug 2018 12:11:41 -0400 Subject: [PATCH 0216/3726] Drop jquery dependency in IPython.display.Javascript --- IPython/core/display.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 41d937807b8..521cae814a0 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -852,18 +852,24 @@ def _data_and_metadata(self): def _repr_json_(self): return self._data_and_metadata() -_css_t = """$("head").append($("").attr({ - rel: "stylesheet", - type: "text/css", - href: "%s" -})); +_css_t = """var link = document.createElement("link"); + link.ref = "stylesheet"; + link.type = "text/css"; + link.href = "%s"; + document.head.appendChild(link); """ -_lib_t1 = """$.getScript("%s", function () { -""" -_lib_t2 = """}); +_lib_t1 = """var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = "%s"; + script.onload = script.onreadystatechange = function() { + if (!this.readyState || this.readyState == 'complete') { """ +_lib_t2 = """} + }; + document.head.appendChild(script);""" + class GeoJSON(JSON): """GeoJSON expects JSON-able dict From b6c88e498ecaadad699f899c3deb594ddf693cb4 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 11:38:46 -0700 Subject: [PATCH 0217/3726] remove duplicate WatsNew from bad rebase --- .travis.yml | 2 +- IPython/__init__.py | 6 +- docs/source/whatsnew/development.rst | 106 --------------------------- setup.py | 9 +-- 4 files changed, 8 insertions(+), 115 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57bfce2234a..eee369d1c8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,9 @@ language: python python: - "nightly" - "3.7-dev" + - 3.7 - 3.6 - 3.5 - - 3.4 sudo: false env: global: diff --git a/IPython/__init__.py b/IPython/__init__.py index 0b78eca3df9..7bd0ee3c464 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -27,12 +27,12 @@ #----------------------------------------------------------------------------- # Don't forget to also update setup.py when this changes! -if sys.version_info < (3,4): +if sys.version_info < (3, 5): raise ImportError( """ -IPython 7.0+ supports Python 3.4 and above. +IPython 7.0+ supports Python 3.5 and above. When using Python 2.7, please install IPython 5.x LTS Long Term Support version. -Python 3.3 was supported up to IPython 6.x. +Python 3.3 and 3.4 were supported up to IPython 6.x. See IPython `README.rst` file for more information: diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 4a62f47a40f..a52005c48c9 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -34,112 +34,6 @@ You should be able to update by using: pip install ipython --upgrade -New completion API and Interface --------------------------------- - -The completer Completion API has seen an overhaul, and the new completer have -plenty of improvement both from the end users of terminal IPython or for -consumers of the API. - -This new API is capable of pulling completions from :any:`jedi`, thus allowing -type inference on non-executed code. If :any:`jedi` is installed completion like -the following are now becoming possible without code evaluation: - - >>> data = ['Number of users', 123_456] - ... data[0]. - -That is to say, IPython is now capable of inferring that `data[0]` is a string, -and will suggest completions like `.capitalize`. The completion power of IPython -will increase with new Jedi releases, and a number of bugs and more completions -are already available on development version of :any:`jedi` if you are curious. - -With the help of prompt toolkit, types of completions can be shown in the -completer interface: - -.. image:: ../_images/jedi_type_inference_60.png - :alt: Jedi showing ability to do type inference - :align: center - :width: 400px - :target: ../_images/jedi_type_inference_60.png - -The appearance of the completer is controlled by the -``c.TerminalInteractiveShell.display_completions`` option that will show the -type differently depending on the value among ``'column'``, ``'multicolumn'`` -and ``'readlinelike'`` - -The use of Jedi also full fill a number of request and fix a number of bugs -like case insensitive completion, completion after division operator: See -:ghpull:`10182`. - -Extra patches and updates will be needed to the :mod:`ipykernel` package for -this feature to be available to other clients like jupyter Notebook, Lab, -Nteract, Hydrogen... - -The use of Jedi can is barely noticeable on recent enough machines, but can be -feel on older ones, in cases were Jedi behavior need to be adjusted, the amount -of time given to Jedi to compute type inference can be adjusted with -``c.IPCompleter.jedi_compute_type_timeout``, with object whose type were not -inferred will be shown as ````. Jedi can also be completely deactivated -by using the ``c.Completer.use_jedi=False`` option. - - -The old ``Completer.complete()`` API is waiting deprecation and should be -replaced replaced by ``Completer.completions()`` in a near future. Feedback on -the current state of the API and suggestions welcome. - -Python 3 only codebase ----------------------- - -One of the large challenges in IPython 6.0 has been the adoption of a pure -Python 3 code base, which lead us to great length to upstream patches in pip, -pypi and warehouse to make sure Python 2 system still upgrade to the latest -compatible Python version compatible. - -We remind our Python 2 users that IPython 5 is still compatible with Python 2.7, -still maintained and get regular releases. Using pip 9+, upgrading IPython will -automatically upgrade to the latest version compatible with your system. - -.. warning:: - - If you are on a system using an older verison of pip on Python 2, pip may - still install IPython 6.0 on your system, and IPython will refuse to start. - You can fix this by ugrading pip, and reinstalling ipython, or forcing pip to - install an earlier version: ``pip install 'ipython<6'`` - -The ability to use only Python 3 on the code base of IPython has bring a number -of advantage. Most of the newly written code make use of `optional function type -anotation `_ leading to clearer code -and better documentation. - -The total size of the repository has also for a first time between releases -(excluding the big split for 4.0) decreased by about 1500 lines, potentially -quite a bit more codewide as some documents like this one are append only and -are about 300 lines long. - -The removal as of Python2/Python3 shim layer has made the code quite clearer and -more idiomatic in a number of location, and much friendlier to work with and -understand. We hope to further embrace Python 3 capability in the next release -cycle and introduce more of the Python 3 only idioms (yield from, kwarg only, -general unpacking) in the code base of IPython, and see if we can take advantage -of these as well to improve user experience with better error messages and -hints. - - -Miscs improvements ------------------- - - -- The :cellmagic:`capture` magic can now capture the result of a cell (from an - expression on the last line), as well as printed and displayed output. - :ghpull:`9851`. - -- Pressing Ctrl-Z in the terminal debugger now suspends IPython, as it already - does in the main terminal prompt. - -- autoreload can now reload ``Enum``. See :ghissue:`10232` and :ghpull:`10316` - -- IPython.display has gained a :any:`GeoJSON ` object. - :ghpull:`10288` and :ghpull:`10253` .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. diff --git a/setup.py b/setup.py index ed188415d92..78529cf6dbc 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ # # This check is also made in IPython/__init__, don't forget to update both when # changing Python version requirements. -if sys.version_info < (3, 4): +if sys.version_info < (3, 5): pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' try: import pip @@ -42,9 +42,9 @@ error = """ -IPython 7.0+ supports Python 3.4 and above. +IPython 7.0+ supports Python 3.5 and above. When using Python 2.7, please install IPython 5.x LTS Long Term Support version. -Python 3.3 was supported up to IPython 6.x. +Python 3.3 and 3.4 were supported up to IPython 6.x. See IPython `README.rst` file for more information: @@ -201,7 +201,6 @@ extras_require.update({ ':python_version == "3.4"': ['typing'], - ':python_version >= "3.5"': ['trio', 'curio'], ':sys_platform != "win32"': ['pexpect'], ':sys_platform == "darwin"': ['appnope'], ':sys_platform == "win32"': ['colorama'], @@ -231,7 +230,7 @@ extras_require['all'] = everything if 'setuptools' in sys.modules: - setuptools_extra_args['python_requires'] = '>=3.4' + setuptools_extra_args['python_requires'] = '>=3.5' setuptools_extra_args['zip_safe'] = False setuptools_extra_args['entry_points'] = { 'console_scripts': find_entry_points(), From 702b9463a72045028f4173cc8556e65206b3207f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 11:56:44 -0700 Subject: [PATCH 0218/3726] test with trio --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78529cf6dbc..af488994ae5 100755 --- a/setup.py +++ b/setup.py @@ -175,7 +175,7 @@ parallel = ['ipyparallel'], qtconsole = ['qtconsole'], doc = ['Sphinx>=1.3'], - test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy'], + test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy', 'trio'], terminal = [], kernel = ['ipykernel'], nbformat = ['nbformat'], From c447030c92fe7d5fec5101ee574bb90a3212b639 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 12:07:37 -0700 Subject: [PATCH 0219/3726] 3.7 still nto on travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eee369d1c8c..86c89a41d74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python python: - "nightly" - "3.7-dev" - - 3.7 - 3.6 - 3.5 sudo: false From 3c0b3aa8c8169544ca4f03cfa1743280d1b058cc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 12:09:19 -0700 Subject: [PATCH 0220/3726] reformat with black --- IPython/core/async_helpers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 02bc2b52e5e..c752dbd2344 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -9,7 +9,6 @@ """ - import ast import sys import inspect @@ -22,6 +21,7 @@ def _asyncio_runner(coro): Handler for asyncio autoawait """ import asyncio + return asyncio.get_event_loop().run_until_complete(coro) @@ -30,12 +30,14 @@ def _curio_runner(coroutine): handler for curio autoawait """ import curio + return curio.run(coroutine) if sys.version_info > (3, 5): # nose refuses to avoid this file and async def is invalidsyntax - s = dedent(''' + s = dedent( + ''' def _trio_runner(function): import trio async def loc(coro): @@ -45,7 +47,8 @@ async def loc(coro): """ return await coro return trio.run(loc, function) - ''') + ''' + ) exec(s, globals(), locals()) @@ -54,13 +57,15 @@ def _asyncify(code: str) -> str: And setup a bit of context to run it later. """ - res = dedent(""" + res = dedent( + """ async def __wrapper__(): try: {usercode} finally: locals() - """).format(usercode=indent(code, ' ' * 8)[8:]) + """ + ).format(usercode=indent(code, " " * 8)[8:]) return res @@ -79,7 +84,7 @@ def _should_be_async(cell: str) -> bool: try: # we can't limit ourself to ast.parse, as it __accepts__ to parse on # 3.7+, but just does not _compile_ - compile(cell, '<>', 'exec') + compile(cell, "<>", "exec") return False except SyntaxError: try: From 390bb17a74b418dac9739e8a7fde446adfc735aa Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 12:34:25 -0700 Subject: [PATCH 0221/3726] doc, remove appveyor 3.4 --- IPython/core/async_helpers.py | 1 + IPython/core/interactiveshell.py | 2 +- appveyor.yml | 4 --- docs/source/interactive/autoawait.rst | 46 ++++++++++++++++----------- docs/source/whatsnew/development.rst | 28 ++++++++++++++++ 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index c752dbd2344..8e6bf81b7b3 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -89,6 +89,7 @@ def _should_be_async(cell: str) -> bool: except SyntaxError: try: ast.parse(_asyncify(cell)) + # TODO verify ast has not "top level" return or yield. except SyntaxError: return False return True diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index e3f146ac265..49ae75d34bc 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -154,7 +154,7 @@ def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: # async integration from .async_helpers import (_asyncio_runner, _asyncify) -if sys.version_info > (3,5): +if sys.version_info > (3, 5): from .async_helpers import _curio_runner, _trio_runner, _should_be_async else : _curio_runner = _trio_runner = None diff --git a/appveyor.yml b/appveyor.yml index c22865fde3a..7fbecff4318 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,10 +8,6 @@ environment: PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "32" - - PYTHON: "C:\\Python34-x64" - PYTHON_VERSION: "3.4.x" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6.x" PYTHON_ARCH: "64" diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 35bd4e787a9..003bf56898a 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -3,12 +3,17 @@ Asynchronous in REPL: Autoawait =============================== -Starting with IPython 6.0, and when user Python 3.6 and above, IPython offer the -ability to run asynchronous code from the REPL. constructs which are +Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the +ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. -When a supported libray is used, IPython will automatically `await` Futures -and Coroutines in the REPL. This will happen if an :ref:`await ` (or `async`) is +The example given here are for terminal IPython, running async code in a +notebook interface or any other frontend using the Jupyter protocol will need to +use a newer version of IPykernel. The details of how async code runs in +IPykernel will differ between IPython, IPykernel and their versions. + +When a supported library is used, IPython will automatically `await` Futures +and Coroutines in the REPL. This will happen if an :ref:`await ` is use at top level scope, or if any structure valid only in `async def `_ function context are present. For example, the following being a syntax error in the @@ -29,7 +34,7 @@ Should behave as expected in the IPython REPL:: Python 3.6.0 Type 'copyright', 'credits' or 'license' for more information - IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help. + IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import aiohttp ...: result = aiohttp.get('https://api.github.com') @@ -58,7 +63,9 @@ use the :magic:`%autoawait` magic to toggle the behavior at runtime:: By default IPython will assume integration with Python's provided :mod:`asyncio`, but integration with other libraries is provided. In particular -we provide experimental integration with the ``curio`` and ``trio`` library. +we provide experimental integration with the ``curio`` and ``trio`` library, the +later one being necessary if you require the ability to do nested call of +IPython's ``embed()`` functionality. You can switch current integration by using the ``c.InteractiveShell.loop_runner`` option or the ``autoawait Date: Thu, 16 Aug 2018 12:38:53 -0700 Subject: [PATCH 0222/3726] move infor to the right place --- docs/source/whatsnew/development.rst | 26 -------------------------- docs/source/whatsnew/pr/await-repl.rst | 24 +++++++++++++++++++----- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index ca16bd65b93..b8b2f004fb7 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -35,32 +35,6 @@ You should be able to update by using: pip install ipython --upgrade -Autowait: Asynchronous REPL -=========================== - -Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await -code at top level, you should not need to access an event loop or runner -yourself. To know more read the `autoawait`_ section of our docs, or try the -following code:: - - In [6]: from asyncio import sleep - ...: print('Going to sleep...') - ...: await sleep(3) - ...: print('Waking up') - Going to sleep... - Waking up - -Asynchronous code in a Notebook interface or any other frontend using the -Jupyter Protocol will need further updates of the IPykernel package. - - -Change to Nested Embed -====================== - -The introduction of the ability to run async code had ripple effect on the -ability to use nested IPython. You may need to install the ``trio`` library -(version 05 at the time of this writing) to -have this feature working. .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. diff --git a/docs/source/whatsnew/pr/await-repl.rst b/docs/source/whatsnew/pr/await-repl.rst index 614a00a5a4c..c8f191c9c4e 100644 --- a/docs/source/whatsnew/pr/await-repl.rst +++ b/docs/source/whatsnew/pr/await-repl.rst @@ -1,12 +1,14 @@ -Await REPL ----------- +Autowait: Asynchronous REPL +--------------------------- -:ghpull:`10390` introduced the ability to ``await`` Futures and -Coroutines in the REPL. For example:: +Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await +code at top level, you should not need to access an event loop or runner +yourself. To know more read the :ref:`autoawait` section of our docs, see +:ghpull:`11265` or try the following code:: Python 3.6.0 Type 'copyright', 'credits' or 'license' for more information - IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help. + IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import aiohttp ...: result = aiohttp.get('https://api.github.com') @@ -52,4 +54,16 @@ like ``curio`` or ``trio``, to improve concurrency in the REPL:: See :ref:`autoawait` for more information. +Asynchronous code in a Notebook interface or any other frontend using the +Jupyter Protocol will need further updates of the IPykernel package. + + +Change to Nested Embed +---------------------- + +The introduction of the ability to run async code had ripple effect on the +ability to use nested IPython. You may need to install the ``trio`` library +(version 05 at the time of this writing) to +have this feature working. + From da3e46e500b162faf272889f0c988765bf657c3a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 12:46:42 -0700 Subject: [PATCH 0223/3726] DeprecationWarning --- IPython/lib/tests/test_pretty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index d1c470bea3e..cd9fb3662f2 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -413,7 +413,7 @@ def test_pretty_environ(): # reindent to align with 'environ' prefix dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ'))) env_repr = pretty.pretty(os.environ) - nt.assert_equals(env_repr, 'environ' + dict_indented) + nt.assert_equal(env_repr, 'environ' + dict_indented) def test_function_pretty(): From 521e8e576872add1f8a7c7ec4e3add0001430ab6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 12:48:08 -0700 Subject: [PATCH 0224/3726] runblack on new tests --- IPython/core/tests/test_async_helpers.py | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 6c542085eed..338b43941f3 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -12,41 +12,48 @@ ip = get_ipython() iprc = lambda x: ip.run_cell(dedent(x)) -if sys.version_info > (3,5): +if sys.version_info > (3, 5): from IPython.core.async_helpers import _should_be_async class AsyncTest(TestCase): - def test_should_be_async(self): nt.assert_false(_should_be_async("False")) nt.assert_true(_should_be_async("await bar()")) nt.assert_true(_should_be_async("x = await bar()")) - nt.assert_false(_should_be_async(dedent(""" + nt.assert_false( + _should_be_async( + dedent( + """ async def awaitable(): pass - """))) + """ + ) + ) + ) def test_execute(self): - iprc(""" + iprc( + """ import asyncio await asyncio.sleep(0.001) - """) + """ + ) def test_autoawait(self): - ip.run_cell('%autoawait False') - ip.run_cell('%autoawait True') - iprc(''' + ip.run_cell("%autoawait False") + ip.run_cell("%autoawait True") + iprc( + """ from asyncio import sleep await.sleep(0.1) - ''') + """ + ) def test_autoawait_curio(self): - ip.run_cell('%autoawait curio') + ip.run_cell("%autoawait curio") def test_autoawait_trio(self): - ip.run_cell('%autoawait trio') + ip.run_cell("%autoawait trio") def tearDown(self): - ip.loop_runner = 'asyncio' - - + ip.loop_runner = "asyncio" From 05a55ad45feff731284703d0292a660ec29c7aa6 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Fri, 17 Aug 2018 14:18:48 -0400 Subject: [PATCH 0225/3726] Take jquery's getScript approach --- IPython/core/display.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 521cae814a0..ca5c3aa5b86 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -859,16 +859,17 @@ def _repr_json_(self): document.head.appendChild(link); """ -_lib_t1 = """var script = document.createElement("script"); - script.type = "text/javascript"; +_lib_t1 = """new Promise(function(resolve, reject) { + var script = document.createElement("script"); + script.onload = resolve; + script.onerror = reject; script.src = "%s"; - script.onload = script.onreadystatechange = function() { - if (!this.readyState || this.readyState == 'complete') { + document.head.appendChild(script); +}).then(() => { """ -_lib_t2 = """} - }; - document.head.appendChild(script);""" +_lib_t2 = """ +});""" class GeoJSON(JSON): """GeoJSON expects JSON-able dict From 9f6be1555e2010aac9f592fee22e6de2f479cbff Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 19 Aug 2018 11:40:49 -0700 Subject: [PATCH 0226/3726] att triio runner at top level now that 3.4 drop + docs --- IPython/core/async_helpers.py | 25 +++++++++---------------- docs/source/interactive/autoawait.rst | 16 +++++++++++++--- docs/source/whatsnew/pr/await-repl.rst | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 8e6bf81b7b3..1a93d2ff2f2 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -34,22 +34,15 @@ def _curio_runner(coroutine): return curio.run(coroutine) -if sys.version_info > (3, 5): - # nose refuses to avoid this file and async def is invalidsyntax - s = dedent( - ''' - def _trio_runner(function): - import trio - async def loc(coro): - """ - We need the dummy no-op async def to protect from - trio's internal. See https://github.com/python-trio/trio/issues/89 - """ - return await coro - return trio.run(loc, function) - ''' - ) - exec(s, globals(), locals()) +def _trio_runner(function): + import trio + async def loc(coro): + """ + We need the dummy no-op async def to protect from + trio's internal. See https://github.com/python-trio/trio/issues/89 + """ + return await coro + return trio.run(loc, function) def _asyncify(code: str) -> str: diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 003bf56898a..34f4b5357e9 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -12,9 +12,10 @@ notebook interface or any other frontend using the Jupyter protocol will need to use a newer version of IPykernel. The details of how async code runs in IPykernel will differ between IPython, IPykernel and their versions. -When a supported library is used, IPython will automatically `await` Futures -and Coroutines in the REPL. This will happen if an :ref:`await ` is -use at top level scope, or if any structure valid only in `async def +When a supported library is used, IPython will automatically `await` Futures and +Coroutines in the REPL. This will happen if an :ref:`await ` (or any +other async constructs like async-with, async-for) is use at top level scope, or +if any structure valid only in `async def `_ function context are present. For example, the following being a syntax error in the Python REPL:: @@ -117,6 +118,15 @@ started only when awaited for the first time. That is to say, in first example, no network request is done between ``In[1]`` and ``In[2]``. +Effects on IPython.embed() +========================== + +IPython core being synchronous, the use of ``IPython.embed()`` will now require +a loop to run. This affect the ability to nest ``IPython.embed()`` which may +require you to install alternate IO libraries like ``curio`` and ``trio`` + + + Internals ========= diff --git a/docs/source/whatsnew/pr/await-repl.rst b/docs/source/whatsnew/pr/await-repl.rst index c8f191c9c4e..e766e741ddb 100644 --- a/docs/source/whatsnew/pr/await-repl.rst +++ b/docs/source/whatsnew/pr/await-repl.rst @@ -63,7 +63,7 @@ Change to Nested Embed The introduction of the ability to run async code had ripple effect on the ability to use nested IPython. You may need to install the ``trio`` library -(version 05 at the time of this writing) to +(version 0.5 at the time of this writing) to have this feature working. From 2b7f067af382024fc37371c224d3a77427b3f134 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 19 Aug 2018 12:22:19 -0700 Subject: [PATCH 0227/3726] Add pseudo sync mode --- IPython/core/async_helpers.py | 22 ++++++++++++++++++++-- IPython/core/interactiveshell.py | 15 +++++++++------ IPython/core/magics/basic.py | 10 +++++++--- IPython/terminal/embed.py | 4 ++-- docs/source/interactive/autoawait.rst | 11 +++++++++-- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 1a93d2ff2f2..a5affd05407 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -34,7 +34,7 @@ def _curio_runner(coroutine): return curio.run(coroutine) -def _trio_runner(function): +def _trio_runner(async_fn): import trio async def loc(coro): """ @@ -42,7 +42,25 @@ async def loc(coro): trio's internal. See https://github.com/python-trio/trio/issues/89 """ return await coro - return trio.run(loc, function) + return trio.run(loc, async_fn) + + +def _pseudo_sync_runner(coro): + """ + A runner that does not really allow async execution, and just advance the coroutine. + + See discussion in https://github.com/python-trio/trio/issues/608, + + Credit to Nathaniel Smith + + """ + try: + coro.send(None) + except StopIteration as exc: + return exc.value + else: + # TODO: do not raise but return an execution result with the right info. + raise RuntimeError(f"{coro.__name__!r} needs a real async loop") def _asyncify(code: str) -> str: diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 49ae75d34bc..dbb45921771 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -152,7 +152,7 @@ def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: # we still need to run things using the asyncio eventloop, but there is no # async integration -from .async_helpers import (_asyncio_runner, _asyncify) +from .async_helpers import (_asyncio_runner, _asyncify, _pseudo_sync_runner) if sys.version_info > (3, 5): from .async_helpers import _curio_runner, _trio_runner, _should_be_async @@ -365,9 +365,10 @@ class InteractiveShell(SingletonConfigurable): ).tag(config=True) loop_runner_map ={ - 'asyncio':_asyncio_runner, - 'curio':_curio_runner, - 'trio':_trio_runner, + 'asyncio':(_asyncio_runner, True), + 'curio':(_curio_runner, True), + 'trio':(_trio_runner, True), + 'sync': (_pseudo_sync_runner, False) } loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner", @@ -383,7 +384,9 @@ def _default_loop_runner(self): def _import_runner(self, proposal): if isinstance(proposal.value, str): if proposal.value in self.loop_runner_map: - return self.loop_runner_map[proposal.value] + runner, autoawait = self.loop_runner_map[proposal.value] + self.autoawait = autoawait + return runner runner = import_item(proposal.value) if not callable(runner): raise ValueError('loop_runner must be callable') @@ -2815,7 +2818,7 @@ def _run_cell(self, raw_cell, store_history, silent, shell_futures): ) @asyncio.coroutine - def run_cell_async(self, raw_cell, store_history=False, silent=False, shell_futures=True): + def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult: """Run a complete IPython cell asynchronously. Parameters diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index c60a1934b9f..af0ab4da1ae 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -622,9 +622,14 @@ def autoawait(self, parameter_s): - asyncio/curio/trio activate autoawait integration and use integration with said library. + - `sync` turn on the pseudo-sync integration (mostly used for + `IPython.embed()` which does not run IPython with a real eventloop and + deactivate running asynchronous code. Turning on Asynchronous code with + the pseudo sync loop is undefined behavior and may lead IPython to crash. + If the passed parameter does not match any of the above and is a python identifier, get said object from user namespace and set it as the - runner, and activate autoawait. + runner, and activate autoawait. If the object is a fully qualified object name, attempt to import it and set it as the runner, and activate autoawait.""" @@ -647,8 +652,7 @@ def autoawait(self, parameter_s): return None if param in self.shell.loop_runner_map: - self.shell.loop_runner = param - self.shell.autoawait = True + self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param] return None if param in self.shell.user_ns : diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index fa0345c3245..ef519ea8171 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -383,9 +383,9 @@ def embed(**kwargs): config = load_default_config() config.InteractiveShellEmbed = config.TerminalInteractiveShell kwargs['config'] = config - using = kwargs.get('using', 'trio') + using = kwargs.get('using', 'sync') if using : - kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor'}}) + kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}}) #save ps1/ps2 if defined ps1 = None ps2 = None diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 34f4b5357e9..4a60e4ec42e 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -121,8 +121,15 @@ no network request is done between ``In[1]`` and ``In[2]``. Effects on IPython.embed() ========================== -IPython core being synchronous, the use of ``IPython.embed()`` will now require -a loop to run. This affect the ability to nest ``IPython.embed()`` which may +IPython core being asynchronous, the use of ``IPython.embed()`` will now require +a loop to run. In order to allow ``IPython.embed()`` to be nested, as most event +loops can't be nested, ``IPython.embed()`` default to a pseudo-synchronous mode, +where async code is not allowed. This mode is available in classical IPython +using ``%autoawait sync`` + + + +This affect the ability to nest ``IPython.embed()`` which may require you to install alternate IO libraries like ``curio`` and ``trio`` From 58c1419a5e90cdb4589ed134d40eb4fafc84f588 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 19 Aug 2018 14:15:37 -0700 Subject: [PATCH 0228/3726] no f-strings --- IPython/core/async_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index a5affd05407..d334dba72cb 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -60,7 +60,7 @@ def _pseudo_sync_runner(coro): return exc.value else: # TODO: do not raise but return an execution result with the right info. - raise RuntimeError(f"{coro.__name__!r} needs a real async loop") + raise RuntimeError("{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)) def _asyncify(code: str) -> str: From 8d38b3ce41953d8d9048092cc283eb7662a768ed Mon Sep 17 00:00:00 2001 From: Dale Jung Date: Tue, 21 Aug 2018 12:54:43 -0400 Subject: [PATCH 0229/3726] Only trigger async loop runner when needed. Allows running files that call async loops synchronously on their own. https://github.com/ipython/ipython/pull/11265 Handle early return conditions from coro --- IPython/core/interactiveshell.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index dbb45921771..3f4950d27d4 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2808,15 +2808,25 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr def _run_cell(self, raw_cell, store_history, silent, shell_futures): """Internal method to run a complete IPython cell.""" - return self.loop_runner( - self.run_cell_async( - raw_cell, - store_history=store_history, - silent=silent, - shell_futures=shell_futures, - ) + coro = self.run_cell_async( + raw_cell, + store_history=store_history, + silent=silent, + shell_futures=shell_futures, ) + try: + interactivity = coro.send(None) + except StopIteration as exc: + return exc.value + + if isinstance(interactivity, ExecutionResult): + return interactivity + + if interactivity == 'async': + return self.loop_runner(coro) + return _pseudo_sync_runner(coro) + @asyncio.coroutine def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult: """Run a complete IPython cell asynchronously. @@ -2965,6 +2975,9 @@ def error_before_exec(value): interactivity = "none" if silent else self.ast_node_interactivity if _run_async: interactivity = 'async' + # yield interactivity so let run_cell decide whether to use + # an async loop_runner + yield interactivity has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name, interactivity=interactivity, compiler=compiler, result=result) From c4bf6326fe0726afb7d36bc8b67ed3a30ac09809 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 26 Aug 2018 15:18:59 -0400 Subject: [PATCH 0230/3726] Improve async detection mechanism with blacklist Because the async repl works by wrapping any code that raises SyntaxError in an async function and trying to execute it again, cell bodies that are invalid at the top level but valid in functions and methods (e.g. return and yield statements) currently allow executing invalid code. This patch blacklists return and yield statements outside of a function or method to restore the proper SyntaxError behavior. --- IPython/core/async_helpers.py | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index d334dba72cb..fa3d5cfb531 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -80,6 +80,40 @@ async def __wrapper__(): return res +class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): + """ + Find syntax errors that would be an error in an async repl, but because + the implementation involves wrapping the repl in an async function, it + is erroneously allowed (e.g. yield or return at the top level) + """ + def generic_visit(self, node): + func_types = (ast.FunctionDef, ast.AsyncFunctionDef) + invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) + + if isinstance(node, func_types): + return # Don't recurse into functions + elif isinstance(node, invalid_types): + raise SyntaxError() + else: + super().generic_visit(node) + + +def _async_parse_cell(cell: str) -> ast.AST: + """ + This is a compatibility shim for pre-3.7 when async outside of a function + is a syntax error at the parse stage. + + It will return an abstract syntax tree parsed as if async and await outside + of a function were not a syntax error. + """ + if sys.version_info < (3, 7): + # Prior to 3.7 you need to asyncify before parse + wrapped_parse_tree = ast.parse(_asyncify(cell)) + return wrapped_parse_tree.body[0].body[0] + else: + return ast.parse(cell) + + def _should_be_async(cell: str) -> bool: """Detect if a block of code need to be wrapped in an `async def` @@ -99,8 +133,12 @@ def _should_be_async(cell: str) -> bool: return False except SyntaxError: try: - ast.parse(_asyncify(cell)) - # TODO verify ast has not "top level" return or yield. + parse_tree = _async_parse_cell(cell) + + # Raise a SyntaxError if there are top-level return or yields + v = _AsyncSyntaxErrorVisitor() + v.visit(parse_tree) + except SyntaxError: return False return True From b210342462a954e5889db88707985cf6a34764d7 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 26 Aug 2018 15:25:01 -0400 Subject: [PATCH 0231/3726] Add tests for SyntaxError with top-level return --- IPython/core/tests/test_async_helpers.py | 180 ++++++++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 338b43941f3..07548846f96 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -6,11 +6,12 @@ import sys import nose.tools as nt -from textwrap import dedent +from textwrap import dedent, indent from unittest import TestCase ip = get_ipython() iprc = lambda x: ip.run_cell(dedent(x)) +iprc_err = lambda x: iprc(x).raise_error() if sys.version_info > (3, 5): from IPython.core.async_helpers import _should_be_async @@ -31,6 +32,183 @@ async def awaitable(): ) ) + def _get_top_level_cases(self): + # These are test cases that should be valid in a function + # but invalid outside of a function. + test_cases = [] + test_cases.append(('basic', "{val}")) + + # Note, in all conditional cases, I use True instead of + # False so that the peephole optimizer won't optimize away + # the return, so CPython will see this as a syntax error: + # + # while True: + # break + # return + # + # But not this: + # + # while False: + # return + # + # See https://bugs.python.org/issue1875 + + test_cases.append(('if', dedent(""" + if True: + {val} + """))) + + test_cases.append(('while', dedent(""" + while True: + {val} + break + """))) + + test_cases.append(('try', dedent(""" + try: + {val} + except: + pass + """))) + + test_cases.append(('except', dedent(""" + try: + pass + except: + {val} + """))) + + test_cases.append(('finally', dedent(""" + try: + pass + except: + pass + finally: + {val} + """))) + + test_cases.append(('for', dedent(""" + for _ in range(4): + {val} + """))) + + + test_cases.append(('nested', dedent(""" + if True: + while True: + {val} + break + """))) + + test_cases.append(('deep-nested', dedent(""" + if True: + while True: + break + for x in range(3): + if True: + while True: + for x in range(3): + {val} + """))) + + return test_cases + + def _get_ry_syntax_errors(self): + # This is a mix of tests that should be a syntax error if + # return or yield whether or not they are in a function + + test_cases = [] + + test_cases.append(('class', dedent(""" + class V: + {val} + """))) + + test_cases.append(('nested-class', dedent(""" + class V: + class C: + {val} + """))) + + return test_cases + + + def test_top_level_return_error(self): + tl_err_test_cases = self._get_top_level_cases() + tl_err_test_cases.extend(self._get_ry_syntax_errors()) + + vals = ('return', 'yield', 'yield from (_ for _ in range(3))') + + for test_name, test_case in tl_err_test_cases: + # This example should work if 'pass' is used as the value + with self.subTest((test_name, 'pass')): + iprc_err(test_case.format(val='pass')) + + # It should fail with all the values + for val in vals: + with self.subTest((test_name, val)): + msg = "Syntax error not raised for %s, %s" % (test_name, val) + with self.assertRaises(SyntaxError, msg=msg): + iprc_err(test_case.format(val=val)) + + def test_in_func_no_error(self): + # Test that the implementation of top-level return/yield + # detection isn't *too* aggressive, and works inside a function + func_contexts = [] + + func_contexts.append(('func', dedent(""" + def f():"""))) + + func_contexts.append(('method', dedent(""" + class MyClass: + def __init__(self): + """))) + + func_contexts.append(('async-func', dedent(""" + async def f():"""))) + + func_contexts.append(('closure', dedent(""" + def f(): + def g(): + """))) + + def nest_case(context, case): + # Detect indentation + lines = context.strip().splitlines() + prefix_len = 0 + for c in lines[-1]: + if c != ' ': + break + prefix_len += 1 + + indented_case = indent(case, ' ' * (prefix_len + 4)) + return context + '\n' + indented_case + + # Gather and run the tests + vals = ('return', 'yield') + + success_tests = self._get_top_level_cases() + failure_tests = self._get_ry_syntax_errors() + + for context_name, context in func_contexts: + # These tests should now successfully run + for test_name, test_case in success_tests: + nested_case = nest_case(context, test_case) + + for val in vals: + with self.subTest((test_name, context_name, val)): + iprc_err(nested_case.format(val=val)) + + # These tests should still raise a SyntaxError + for test_name, test_case in failure_tests: + nested_case = nest_case(context, test_case) + + for val in vals: + with self.subTest((test_name, context_name, val)): + with self.assertRaises(SyntaxError): + iprc_err(nested_case.format(val=val)) + + def test_execute(self): iprc( """ From 422664acd4c9d47fb2164710afcc7489ea94e332 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 27 Aug 2018 09:52:31 -0400 Subject: [PATCH 0232/3726] Fix indentation problem with _asyncify If the input to `_asyncify` contains newlines, the strategy of indenting and then truncating the first 8 characters won't work, since the first, empty line doesn't get indented. Instead I've changed the format string to accept the normal output of textwrap.indent() without modification. --- IPython/core/async_helpers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index fa3d5cfb531..e8096e02aba 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -69,14 +69,14 @@ def _asyncify(code: str) -> str: And setup a bit of context to run it later. """ res = dedent( - """ - async def __wrapper__(): - try: - {usercode} - finally: - locals() """ - ).format(usercode=indent(code, " " * 8)[8:]) + async def __wrapper__(): + try: + {usercode} + finally: + locals() + """ + ).format(usercode=indent(code, " " * 8)) return res From d0b14965f49473c1dee84ef121d333056938f544 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 27 Aug 2018 09:54:41 -0400 Subject: [PATCH 0233/3726] Fix async_helpers tests Many of the tests were simply executing code and ignoring errors (including several which had errors in them). This commit makes it so that errors are actually raised, and fixes the errors in code that had them. --- IPython/core/tests/test_async_helpers.py | 29 +++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 07548846f96..c294c82667b 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -10,8 +10,7 @@ from unittest import TestCase ip = get_ipython() -iprc = lambda x: ip.run_cell(dedent(x)) -iprc_err = lambda x: iprc(x).raise_error() +iprc = lambda x: ip.run_cell(dedent(x)).raise_error() if sys.version_info > (3, 5): from IPython.core.async_helpers import _should_be_async @@ -142,14 +141,14 @@ def test_top_level_return_error(self): for test_name, test_case in tl_err_test_cases: # This example should work if 'pass' is used as the value with self.subTest((test_name, 'pass')): - iprc_err(test_case.format(val='pass')) + iprc(test_case.format(val='pass')) # It should fail with all the values for val in vals: with self.subTest((test_name, val)): msg = "Syntax error not raised for %s, %s" % (test_name, val) with self.assertRaises(SyntaxError, msg=msg): - iprc_err(test_case.format(val=val)) + iprc(test_case.format(val=val)) def test_in_func_no_error(self): # Test that the implementation of top-level return/yield @@ -197,7 +196,7 @@ def nest_case(context, case): for val in vals: with self.subTest((test_name, context_name, val)): - iprc_err(nested_case.format(val=val)) + iprc(nested_case.format(val=val)) # These tests should still raise a SyntaxError for test_name, test_case in failure_tests: @@ -206,32 +205,30 @@ def nest_case(context, case): for val in vals: with self.subTest((test_name, context_name, val)): with self.assertRaises(SyntaxError): - iprc_err(nested_case.format(val=val)) + iprc(nested_case.format(val=val)) def test_execute(self): - iprc( - """ + iprc(""" import asyncio await asyncio.sleep(0.001) """ ) def test_autoawait(self): - ip.run_cell("%autoawait False") - ip.run_cell("%autoawait True") - iprc( - """ - from asyncio import sleep - await.sleep(0.1) + iprc("%autoawait False") + iprc("%autoawait True") + iprc(""" + from asyncio import sleep + await sleep(0.1) """ ) def test_autoawait_curio(self): - ip.run_cell("%autoawait curio") + iprc("%autoawait curio") def test_autoawait_trio(self): - ip.run_cell("%autoawait trio") + iprc("%autoawait trio") def tearDown(self): ip.loop_runner = "asyncio" From 342427e15bf118e6c208b8f774b3ab882e49b7c9 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 27 Aug 2018 10:51:11 -0400 Subject: [PATCH 0234/3726] Refactor and tweak async-in-function tests This improves Python3.5 compatibility, since "yield" in an async function was a SyntaxError in 3.5, but not in later versions Also adds a test for async methods. --- IPython/core/tests/test_async_helpers.py | 58 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index c294c82667b..748f3f123dd 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -5,6 +5,7 @@ """ import sys +from itertools import chain, repeat import nose.tools as nt from textwrap import dedent, indent from unittest import TestCase @@ -155,18 +156,22 @@ def test_in_func_no_error(self): # detection isn't *too* aggressive, and works inside a function func_contexts = [] - func_contexts.append(('func', dedent(""" + func_contexts.append(('func', False, dedent(""" def f():"""))) - func_contexts.append(('method', dedent(""" + func_contexts.append(('method', False, dedent(""" class MyClass: def __init__(self): """))) - func_contexts.append(('async-func', dedent(""" + func_contexts.append(('async-func', True, dedent(""" async def f():"""))) - func_contexts.append(('closure', dedent(""" + func_contexts.append(('async-method', True, dedent(""" + class MyClass: + async def f(self):"""))) + + func_contexts.append(('closure', False, dedent(""" def f(): def g(): """))) @@ -184,28 +189,41 @@ def nest_case(context, case): return context + '\n' + indented_case # Gather and run the tests - vals = ('return', 'yield') - success_tests = self._get_top_level_cases() - failure_tests = self._get_ry_syntax_errors() + # yield is allowed in async functions, starting in Python 3.6, + # and yield from is not allowed in any version + vals = ('return', 'yield', 'yield from (_ for _ in range(3))') + async_safe = (True, + sys.version_info >= (3, 6), + False) + vals = tuple(zip(vals, async_safe)) - for context_name, context in func_contexts: - # These tests should now successfully run - for test_name, test_case in success_tests: - nested_case = nest_case(context, test_case) + success_tests = zip(self._get_top_level_cases(), repeat(False)) + failure_tests = zip(self._get_ry_syntax_errors(), repeat(True)) - for val in vals: - with self.subTest((test_name, context_name, val)): - iprc(nested_case.format(val=val)) + tests = chain(success_tests, failure_tests) - # These tests should still raise a SyntaxError - for test_name, test_case in failure_tests: + for context_name, async_func, context in func_contexts: + for (test_name, test_case), should_fail in tests: nested_case = nest_case(context, test_case) - for val in vals: - with self.subTest((test_name, context_name, val)): - with self.assertRaises(SyntaxError): - iprc(nested_case.format(val=val)) + for val, async_safe in vals: + val_should_fail = (should_fail or + (async_func and not async_safe)) + + test_id = (context_name, test_name, val) + cell = nested_case.format(val=val) + + with self.subTest(test_id): + if val_should_fail: + msg = ("SyntaxError not raised for %s" % + str(test_id)) + with self.assertRaises(SyntaxError, msg=msg): + iprc(cell) + + print(cell) + else: + iprc(cell) def test_execute(self): From effc242b0625de6c2a9c4cf851b691a9da8bed8d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 27 Aug 2018 14:49:24 -0700 Subject: [PATCH 0235/3726] docs cleanup, reformat code, remove dead code. --- IPython/core/async_helpers.py | 42 +++++++++++++-------- IPython/core/interactiveshell.py | 8 +++- IPython/core/magics/basic.py | 7 +++- IPython/terminal/embed.py | 28 +++----------- docs/source/interactive/autoawait.rst | 51 ++++++++++++++------------ docs/source/whatsnew/pr/await-repl.rst | 32 ++++++++++++++-- 6 files changed, 99 insertions(+), 69 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index e8096e02aba..a6ff86031d0 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -1,28 +1,35 @@ """ Async helper function that are invalid syntax on Python 3.5 and below. -Known limitation and possible improvement. +This code is best effort, and may have edge cases not behaving as expected. In +particular it contain a number of heuristics to detect whether code is +effectively async and need to run in an event loop or not. -Top level code that contain a return statement (instead of, or in addition to -await) will be detected as requiring being wrapped in async calls. This should -be prevented as early return will not work. +Some constructs (like top-level `return`, or `yield`) are taken care of +explicitly to actually raise a SyntaxError and stay as close as possible to +Python semantics. """ import ast import sys -import inspect from textwrap import dedent, indent -from types import CodeType -def _asyncio_runner(coro): - """ - Handler for asyncio autoawait - """ - import asyncio +class _AsyncIORunner: + + def __call__(self, coro): + """ + Handler for asyncio autoawait + """ + import asyncio + + return asyncio.get_event_loop().run_until_complete(coro) - return asyncio.get_event_loop().run_until_complete(coro) + def __str__(self): + return 'asyncio' + +_asyncio_runner = _AsyncIORunner() def _curio_runner(coroutine): @@ -36,12 +43,14 @@ def _curio_runner(coroutine): def _trio_runner(async_fn): import trio + async def loc(coro): """ We need the dummy no-op async def to protect from trio's internal. See https://github.com/python-trio/trio/issues/89 """ return await coro + return trio.run(loc, async_fn) @@ -60,7 +69,9 @@ def _pseudo_sync_runner(coro): return exc.value else: # TODO: do not raise but return an execution result with the right info. - raise RuntimeError("{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)) + raise RuntimeError( + "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) + ) def _asyncify(code: str) -> str: @@ -69,7 +80,7 @@ def _asyncify(code: str) -> str: And setup a bit of context to run it later. """ res = dedent( - """ + """ async def __wrapper__(): try: {usercode} @@ -86,12 +97,13 @@ class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): the implementation involves wrapping the repl in an async function, it is erroneously allowed (e.g. yield or return at the top level) """ + def generic_visit(self, node): func_types = (ast.FunctionDef, ast.AsyncFunctionDef) invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) if isinstance(node, func_types): - return # Don't recurse into functions + return # Don't recurse into functions elif isinstance(node, invalid_types): raise SyntaxError() else: diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 3f4950d27d4..8230a010adb 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2806,7 +2806,7 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr self.events.trigger('post_run_cell', result) return result - def _run_cell(self, raw_cell, store_history, silent, shell_futures): + def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool): """Internal method to run a complete IPython cell.""" coro = self.run_cell_async( raw_cell, @@ -2815,11 +2815,16 @@ def _run_cell(self, raw_cell, store_history, silent, shell_futures): shell_futures=shell_futures, ) + # run_cell_async is async, but may not actually need and eventloop. + # when this is the case, we want to run it using the pseudo_sync_runner + # so that code can invoke eventloops (for example via the %run , and + # `%paste` magic. try: interactivity = coro.send(None) except StopIteration as exc: return exc.value + # if code was not async, sending `None` was actually executing the code. if isinstance(interactivity, ExecutionResult): return interactivity @@ -3159,7 +3164,6 @@ def _async_exec(self, code_obj: types.CodeType, user_ns: dict): return eval(code_obj, user_ns) - @asyncio.coroutine def run_code(self, code_obj, result=None, *, async_=False): """Execute a code object. diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index af0ab4da1ae..6a557fca3cf 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -632,7 +632,12 @@ def autoawait(self, parameter_s): runner, and activate autoawait. If the object is a fully qualified object name, attempt to import it and - set it as the runner, and activate autoawait.""" + set it as the runner, and activate autoawait. + + + The exact behavior of autoawait is experimental and subject to change + across version of IPython and Python. + """ param = parameter_s.strip() d = {True: "on", False: "off"} diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index ef519ea8171..188844faddb 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -19,23 +19,6 @@ from traitlets import Bool, CBool, Unicode from IPython.utils.io import ask_yes_no -from contextlib import contextmanager - -_sentinel = object() -@contextmanager -def new_context(): - import trio._core._run as tcr - old_runner = getattr(tcr.GLOBAL_RUN_CONTEXT, 'runner', _sentinel) - old_task = getattr(tcr.GLOBAL_RUN_CONTEXT, 'task', None) - if old_runner is not _sentinel: - del tcr.GLOBAL_RUN_CONTEXT.runner - tcr.GLOBAL_RUN_CONTEXT.task = None - yield - if old_runner is not _sentinel: - tcr.GLOBAL_RUN_CONTEXT.runner = old_runner - tcr.GLOBAL_RUN_CONTEXT.task = old_task - - class KillEmbedded(Exception):pass # kept for backward compatibility as IPython 6 was released with @@ -400,12 +383,11 @@ def embed(**kwargs): cls = type(saved_shell_instance) cls.clear_instance() frame = sys._getframe(1) - with new_context(): - shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( - frame.f_code.co_filename, frame.f_lineno), **kwargs) - shell(header=header, stack_depth=2, compile_flags=compile_flags, - _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) - InteractiveShellEmbed.clear_instance() + shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( + frame.f_code.co_filename, frame.f_lineno), **kwargs) + shell(header=header, stack_depth=2, compile_flags=compile_flags, + _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) + InteractiveShellEmbed.clear_instance() #restore previous instance if saved_shell_instance is not None: cls = type(saved_shell_instance) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 4a60e4ec42e..90931b16355 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -3,6 +3,11 @@ Asynchronous in REPL: Autoawait =============================== +.. note:: + + This feature is experimental and behavior can change betwen python and + IPython version without prior deprecation. + Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. @@ -12,10 +17,10 @@ notebook interface or any other frontend using the Jupyter protocol will need to use a newer version of IPykernel. The details of how async code runs in IPykernel will differ between IPython, IPykernel and their versions. -When a supported library is used, IPython will automatically `await` Futures and -Coroutines in the REPL. This will happen if an :ref:`await ` (or any -other async constructs like async-with, async-for) is use at top level scope, or -if any structure valid only in `async def +When a supported library is used, IPython will automatically allow Futures and +Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await +` (or any other async constructs like async-with, async-for) is use at +top level scope, or if any structure valid only in `async def `_ function context are present. For example, the following being a syntax error in the Python REPL:: @@ -58,15 +63,13 @@ use the :magic:`%autoawait` magic to toggle the behavior at runtime:: In [1]: %autoawait False In [2]: %autoawait - IPython autoawait is `Off`, and set to use `IPython.core.interactiveshell._asyncio_runner` + IPython autoawait is `Off`, and set to use `asyncio` By default IPython will assume integration with Python's provided :mod:`asyncio`, but integration with other libraries is provided. In particular -we provide experimental integration with the ``curio`` and ``trio`` library, the -later one being necessary if you require the ability to do nested call of -IPython's ``embed()`` functionality. +we provide experimental integration with the ``curio`` and ``trio`` library. You can switch current integration by using the ``c.InteractiveShell.loop_runner`` option or the ``autoawait Date: Mon, 27 Aug 2018 15:21:16 -0700 Subject: [PATCH 0236/3726] remove trio from test requirement and skipp if not there --- IPython/core/tests/test_async_helpers.py | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 748f3f123dd..c219a0f355b 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -9,6 +9,7 @@ import nose.tools as nt from textwrap import dedent, indent from unittest import TestCase +from IPython.testing.decorators import skip_without ip = get_ipython() iprc = lambda x: ip.run_cell(dedent(x)).raise_error() @@ -242,9 +243,11 @@ def test_autoawait(self): """ ) + @skip_without('curio') def test_autoawait_curio(self): iprc("%autoawait curio") + @skip_without('trio') def test_autoawait_trio(self): iprc("%autoawait trio") diff --git a/setup.py b/setup.py index af488994ae5..78529cf6dbc 100755 --- a/setup.py +++ b/setup.py @@ -175,7 +175,7 @@ parallel = ['ipyparallel'], qtconsole = ['qtconsole'], doc = ['Sphinx>=1.3'], - test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy', 'trio'], + test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy'], terminal = [], kernel = ['ipykernel'], nbformat = ['nbformat'], From f384e255144b97194cff1de9cb57a7dfca6f2547 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 27 Aug 2018 15:23:58 -0700 Subject: [PATCH 0237/3726] fix test --- IPython/core/interactiveshell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 8230a010adb..0f6e3a8fad9 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -3164,6 +3164,7 @@ def _async_exec(self, code_obj: types.CodeType, user_ns: dict): return eval(code_obj, user_ns) + @asyncio.coroutine def run_code(self, code_obj, result=None, *, async_=False): """Execute a code object. From b1ccee7b934426fef0f5ded9f76169457faa0713 Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Mon, 27 Aug 2018 19:08:13 -0400 Subject: [PATCH 0238/3726] Misc. typo fixes Found via `codespell -q 3` --- IPython/core/compilerop.py | 2 +- IPython/core/hooks.py | 2 +- IPython/core/prefilter.py | 2 +- IPython/lib/pretty.py | 2 +- IPython/testing/plugin/test_refs.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py index 19b55961122..1744c209c47 100644 --- a/IPython/core/compilerop.py +++ b/IPython/core/compilerop.py @@ -40,7 +40,7 @@ # Constants #----------------------------------------------------------------------------- -# Roughtly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, +# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, # this is used as a bitmask to extract future-related code flags. PyCF_MASK = functools.reduce(operator.or_, (getattr(__future__, fname).compiler_flag diff --git a/IPython/core/hooks.py b/IPython/core/hooks.py index 7cf250aed81..66a544d7d8c 100644 --- a/IPython/core/hooks.py +++ b/IPython/core/hooks.py @@ -174,7 +174,7 @@ def __iter__(self): def shutdown_hook(self): """ default shutdown hook - Typically, shotdown hooks should raise TryNext so all shutdown ops are done + Typically, shutdown hooks should raise TryNext so all shutdown ops are done """ #print "default shutdown hook ok" # dbg diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index 255d42a7f5f..b33bc9c2bd2 100644 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -82,7 +82,7 @@ class PrefilterManager(Configurable): prefilter consumes lines of input and produces transformed lines of input. - The iplementation consists of two phases: + The implementation consists of two phases: 1. Transformers 2. Checkers and handlers diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 90a8c78eece..139d69005cb 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -765,7 +765,7 @@ def _exception_pprint(obj, p, cycle): try: # In PyPy, types.DictProxyType is dict, setting the dictproxy printer - # using dict.setdefault avoids overwritting the dict printer + # using dict.setdefault avoids overwriting the dict printer _type_pprinters.setdefault(types.DictProxyType, _dict_pprinter_factory('dict_proxy({', '})')) _type_pprinters[types.ClassType] = _type_pprint diff --git a/IPython/testing/plugin/test_refs.py b/IPython/testing/plugin/test_refs.py index 50d0857134e..98fc64b92e1 100644 --- a/IPython/testing/plugin/test_refs.py +++ b/IPython/testing/plugin/test_refs.py @@ -21,7 +21,7 @@ def doctest_run(): """ def doctest_runvars(): - """Test that variables defined in scripts get loaded correcly via %run. + """Test that variables defined in scripts get loaded correclty via %run. In [13]: run simplevars.py x is: 1 From ba6166e30c397113592fa3aec51f3cc6a8f54aa5 Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Mon, 27 Aug 2018 19:09:51 -0400 Subject: [PATCH 0239/3726] Whitespace fixes --- IPython/core/compilerop.py | 18 +++++++++--------- IPython/lib/pretty.py | 10 +++++----- IPython/testing/plugin/test_refs.py | 2 +- IPython/utils/frame.py | 5 ++--- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py index 1744c209c47..c5abd061d00 100644 --- a/IPython/core/compilerop.py +++ b/IPython/core/compilerop.py @@ -52,7 +52,7 @@ def code_name(code, number=0): """ Compute a (probably) unique name for code for caching. - + This now expects code to be unicode. """ hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() @@ -71,7 +71,7 @@ class CachingCompiler(codeop.Compile): def __init__(self): codeop.Compile.__init__(self) - + # This is ugly, but it must be done this way to allow multiple # simultaneous ipython instances to coexist. Since Python itself # directly accesses the data structures in the linecache module, and @@ -95,7 +95,7 @@ def __init__(self): def _fix_module_ds(self, module): """ Starting in python 3.7 the AST for mule have changed, and if - the first expressions encountered is a string it is attached to the + the first expressions encountered is a string it is attached to the `docstring` attribute of the `Module` ast node. This breaks IPython, as if this string is the only expression, IPython @@ -108,14 +108,14 @@ def _fix_module_ds(self, module): new_body=[Expr(Str(docstring, lineno=1, col_offset=0), lineno=1, col_offset=0)] new_body.extend(module.body) return fix_missing_locations(Module(new_body)) - + def ast_parse(self, source, filename='', symbol='exec'): """Parse code to an AST with the current compiler flags active. - + Arguments are exactly the same as ast.parse (in the standard library), and are passed to the built-in compile function.""" return self._fix_module_ds(compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)) - + def reset_compiler_flags(self): """Reset compiler flags to default state.""" # This value is copied from codeop.Compile.__init__, so if that ever @@ -127,10 +127,10 @@ def compiler_flags(self): """Flags currently active in the compilation process. """ return self.flags - + def cache(self, code, number=0): """Make a name for a block of code, and cache the code. - + Parameters ---------- code : str @@ -138,7 +138,7 @@ def cache(self, code, number=0): number : int A number which forms part of the code's name. Used for the execution counter. - + Returns ------- The name of the cached code (as a string). Pass this as the filename diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 139d69005cb..39d23ddcd8a 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -104,7 +104,7 @@ def _repr_pretty_(self, p, cycle): def _safe_getattr(obj, attr, default=None): """Safe version of getattr. - + Same as getattr, but will return ``default`` on any Exception, rather than raising. """ @@ -246,7 +246,7 @@ def breakable(self, sep=' '): self.buffer.append(Breakable(sep, width, self)) self.buffer_width += width self._break_outer_groups() - + def break_(self): """ Explicitly insert a newline into the output, maintaining correct indentation. @@ -256,7 +256,7 @@ def break_(self): self.output.write(' ' * self.indentation) self.output_width = self.indentation self.buffer_width = 0 - + def begin_group(self, indent=0, open=''): """ @@ -282,7 +282,7 @@ def begin_group(self, indent=0, open=''): self.group_stack.append(group) self.group_queue.enq(group) self.indentation += indent - + def _enumerate(self, seq): """like enumerate, but with an upper limit on the number of items""" for idx, x in enumerate(seq): @@ -292,7 +292,7 @@ def _enumerate(self, seq): self.text('...') return yield idx, x - + def end_group(self, dedent=0, close=''): """End a group. See `begin_group` for more details.""" self.indentation -= dedent diff --git a/IPython/testing/plugin/test_refs.py b/IPython/testing/plugin/test_refs.py index 98fc64b92e1..bd33942aa11 100644 --- a/IPython/testing/plugin/test_refs.py +++ b/IPython/testing/plugin/test_refs.py @@ -19,7 +19,7 @@ def doctest_run(): In [13]: run simplevars.py x is: 1 """ - + def doctest_runvars(): """Test that variables defined in scripts get loaded correclty via %run. diff --git a/IPython/utils/frame.py b/IPython/utils/frame.py index 1e5e536a5e8..11dab31f8d0 100644 --- a/IPython/utils/frame.py +++ b/IPython/utils/frame.py @@ -49,7 +49,7 @@ def extract_vars(*names,**kw): """ depth = kw.get('depth',0) - + callerNS = sys._getframe(depth+1).f_locals return dict((k,callerNS[k]) for k in names) @@ -58,7 +58,7 @@ def extract_vars_above(*names): """Extract a set of variables by name from another frame. Similar to extractVars(), but with a specified depth of 1, so that names - are exctracted exactly from above the caller. + are extracted exactly from above the caller. This is simply a convenience function so that the very common case (for us) of skipping exactly 1 frame doesn't have to construct a special dict for @@ -93,4 +93,3 @@ def extract_module_locals(depth=0): global_ns = f.f_globals module = sys.modules[global_ns['__name__']] return (module, f.f_locals) - From a1e70fc729d0861b9c8091352bd452ee5860149c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 15:06:29 -0700 Subject: [PATCH 0240/3726] Test that using wrong runner/coroutine pair does not crash. Make sure that if the coroutine runner encounter an exception (likely when encountering the wrong coroutine from another aio library), it is set on the result and does not crash the interpreter. --- IPython/core/interactiveshell.py | 9 ++++++++- IPython/core/tests/test_async_helpers.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 0f6e3a8fad9..8e22b0d1c68 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2829,7 +2829,14 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures return interactivity if interactivity == 'async': - return self.loop_runner(coro) + try: + return self.loop_runner(coro) + except Exception as e: + info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) + result = ExecutionResult(info) + result.error_in_exec = e + self.showtraceback(running_compiled_code=True) + return result return _pseudo_sync_runner(coro) @asyncio.coroutine diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index c219a0f355b..6bf64e2115f 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -13,6 +13,7 @@ ip = get_ipython() iprc = lambda x: ip.run_cell(dedent(x)).raise_error() +iprc_nr = lambda x: ip.run_cell(dedent(x)) if sys.version_info > (3, 5): from IPython.core.async_helpers import _should_be_async @@ -251,5 +252,26 @@ def test_autoawait_curio(self): def test_autoawait_trio(self): iprc("%autoawait trio") + @skip_without('trio') + def test_autoawait_trio_wrong_sleep(self): + iprc("%autoawait trio") + res = iprc_nr(""" + import asyncio + await asyncio.sleep(0) + """) + with nt.assert_raises(TypeError): + res.raise_error() + + @skip_without('trio') + def test_autoawait_asyncio_wrong_sleep(self): + iprc("%autoawait asyncio") + res = iprc_nr(""" + import trio + await trio.sleep(0) + """) + with nt.assert_raises(RuntimeError): + res.raise_error() + + def tearDown(self): ip.loop_runner = "asyncio" From c11291bff4b8f22d14ec67ca13e70267bd111ac1 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 15:09:44 -0700 Subject: [PATCH 0241/3726] run tests with trio and curio on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 86c89a41d74..73d419f90a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ install: - pip install pip --upgrade - pip install setuptools --upgrade - pip install -e file://$PWD#egg=ipython[test] --upgrade + - pip install trio curio - pip install codecov check-manifest --upgrade - sudo apt-get install graphviz script: From f9c89d8d73a3191e44c672b67d4905182f6c16fd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 17:49:36 -0700 Subject: [PATCH 0242/3726] Undeprecate autoindent Autoindent is usefull for some extension that want to use ipython as a subprocess, so un-deprecated it. --- IPython/core/interactiveshell.py | 3 +-- IPython/terminal/magics.py | 2 -- IPython/terminal/shortcuts.py | 10 ++++++++-- docs/source/config/intro.rst | 1 - docs/source/interactive/tutorial.rst | 2 +- docs/source/whatsnew/pr/re-autoindent.rst | 2 ++ 6 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 docs/source/whatsnew/pr/re-autoindent.rst diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 9d17bdf0846..b8fdec27794 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -250,8 +250,7 @@ class InteractiveShell(SingletonConfigurable): objects are automatically called (even if no arguments are present). """ ).tag(config=True) - # TODO: remove all autoindent logic and put into frontends. - # We can't do this yet because even runlines uses the autoindent. + autoindent = Bool(True, help= """ Autoindent IPython code entered interactively. diff --git a/IPython/terminal/magics.py b/IPython/terminal/magics.py index 837aaae7027..b9bcfe8f6e7 100644 --- a/IPython/terminal/magics.py +++ b/IPython/terminal/magics.py @@ -82,8 +82,6 @@ def rerun_pasted(self, name='pasted_block'): @line_magic def autoindent(self, parameter_s = ''): """Toggle autoindent on/off (deprecated)""" - print("%autoindent is deprecated since IPython 5: you can now paste " - "multiple lines without turning autoindentation off.") self.shell.set_autoindent() print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index e3c4364921a..4b99f7ef963 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -112,13 +112,19 @@ def newline_or_execute(event): if not (d.on_last_line or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() ): - b.insert_text('\n' + (' ' * (indent or 0))) + if shell.autoindent: + b.insert_text('\n' + (' ' * (indent or 0))) + else: + b.insert_text('\n') return if (status != 'incomplete') and b.accept_handler: b.validate_and_handle() else: - b.insert_text('\n' + (' ' * (indent or 0))) + if shell.autoindent: + b.insert_text('\n' + (' ' * (indent or 0))) + else: + b.insert_text('\n') return newline_or_execute diff --git a/docs/source/config/intro.rst b/docs/source/config/intro.rst index af8bc79ae3a..115315c9ddb 100644 --- a/docs/source/config/intro.rst +++ b/docs/source/config/intro.rst @@ -67,7 +67,6 @@ Example config file 'mycode.py', 'fancy.ipy' ] - c.InteractiveShell.autoindent = True c.InteractiveShell.colors = 'LightBG' c.InteractiveShell.confirm_exit = False c.InteractiveShell.editor = 'nano' diff --git a/docs/source/interactive/tutorial.rst b/docs/source/interactive/tutorial.rst index f0721107a92..5a70345190b 100644 --- a/docs/source/interactive/tutorial.rst +++ b/docs/source/interactive/tutorial.rst @@ -146,7 +146,7 @@ The built-in magics include: :magic:`macro`, :magic:`recall`, etc. - Functions which affect the shell: :magic:`colors`, :magic:`xmode`, - :magic:`autoindent`, :magic:`automagic`, etc. + :magic:`automagic`, etc. - Other functions such as :magic:`reset`, :magic:`timeit`, :cellmagic:`writefile`, :magic:`load`, or :magic:`paste`. diff --git a/docs/source/whatsnew/pr/re-autoindent.rst b/docs/source/whatsnew/pr/re-autoindent.rst new file mode 100644 index 00000000000..a670d5ff98d --- /dev/null +++ b/docs/source/whatsnew/pr/re-autoindent.rst @@ -0,0 +1,2 @@ +The autoindent feature that was deprecated in 5.x was re-enabled and +un-deprecated in :ghpull:`11257` From 2702d84866973173aaf60d4bb803066a4c59ff14 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 31 Aug 2018 13:57:35 -0700 Subject: [PATCH 0243/3726] add notes some magic don't work yet --- docs/source/interactive/autoawait.rst | 9 +++++++++ docs/source/whatsnew/pr/await-repl.rst | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 90931b16355..0164f93560f 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -132,6 +132,15 @@ allow ``IPython.embed()`` to be nested. Though this will prevent usage of the You can set explicitly a coroutine runner for ``embed()`` if you desire to run asynchronous code, the exact behavior is though undefined. +Effects on Magics +================= + +A couple of magics (``%%timeit``, ``%timeit``, ``%%time``, ``%%prun``) have not +yet been updated to work with asynchronous code and will raise syntax errors +when trying to use top-level ``await``. We welcome any contribution to help fix +those, and extra cases we haven't caught yet. We hope for better support in Cor +Python for top-level Async code. + Internals ========= diff --git a/docs/source/whatsnew/pr/await-repl.rst b/docs/source/whatsnew/pr/await-repl.rst index 2556c2a33c7..d61dc2e848c 100644 --- a/docs/source/whatsnew/pr/await-repl.rst +++ b/docs/source/whatsnew/pr/await-repl.rst @@ -77,6 +77,12 @@ The introduction of the ability to run async code had some effect on the ``IPython.embed()`` API. By default embed will not allow you to run asynchronous code unless a event loop is specified. +Effects on Magics +----------------- + +Some magics will not work with Async, and will need updates. Contribution +welcome. + Expected Future changes ----------------------- From 0f7f262484c24b9bcecb4fd7f539e8a176dde55d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 1 Sep 2018 14:10:02 -0700 Subject: [PATCH 0244/3726] Actually run some tests. The ipdoctest was relying on the side-effects of applying `map()` which is lazy since Python3. Moving to an actual for loop. Closes #11276 --- IPython/testing/ipunittest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/testing/ipunittest.py b/IPython/testing/ipunittest.py index cb5060f4efe..aab386197e9 100644 --- a/IPython/testing/ipunittest.py +++ b/IPython/testing/ipunittest.py @@ -146,7 +146,8 @@ class Tester(unittest.TestCase): def test(self): # Make a new runner per function to be tested runner = DocTestRunner(verbose=d2u.verbose) - map(runner.run, d2u.finder.find(func, func.__name__)) + for the_test in d2u.finder.find(func, func.__name__): + runner.run(the_test) failed = count_failures(runner) if failed: # Since we only looked at a single function's docstring, From 468659656b0891b9cf2fb0ef993ef5e89eb756d7 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 1 Sep 2018 16:40:37 -0700 Subject: [PATCH 0245/3726] Remove Python 2 shim. There was some recent bugs where doctests were not ran, so slowly clearing and checking other doctest for later refactor --- IPython/testing/plugin/test_ipdoctest.py | 9 +++------ IPython/utils/py3compat.py | 8 -------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/IPython/testing/plugin/test_ipdoctest.py b/IPython/testing/plugin/test_ipdoctest.py index a7add7da792..c14bc150761 100644 --- a/IPython/testing/plugin/test_ipdoctest.py +++ b/IPython/testing/plugin/test_ipdoctest.py @@ -8,23 +8,21 @@ """ from IPython.utils.py3compat import doctest_refactor_print -@doctest_refactor_print def doctest_simple(): """ipdoctest must handle simple inputs In [1]: 1 Out[1]: 1 - In [2]: print 1 + In [2]: print(1) 1 """ -@doctest_refactor_print def doctest_multiline1(): """The ipdoctest machinery must handle multiline examples gracefully. In [2]: for i in range(4): - ...: print i + ...: print(i) ...: 0 1 @@ -32,7 +30,6 @@ def doctest_multiline1(): 3 """ -@doctest_refactor_print def doctest_multiline2(): """Multiline examples that define functions and print output. @@ -44,7 +41,7 @@ def doctest_multiline2(): Out[8]: 2 In [9]: def g(x): - ...: print 'x is:',x + ...: print('x is:',x) ...: In [10]: g(1) diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py index 27a82766848..e5716650392 100644 --- a/IPython/utils/py3compat.py +++ b/IPython/utils/py3compat.py @@ -193,14 +193,6 @@ def _print_statement_sub(match): expr = match.groups('expr') return "print(%s)" % expr -@_modify_str_or_docstring -def doctest_refactor_print(doc): - """Refactor 'print x' statements in a doctest to print(x) style. 2to3 - unfortunately doesn't pick up on our doctests. - - Can accept a string or a function, so it can be used as a decorator.""" - return _print_statement_re.sub(_print_statement_sub, doc) - # Abstract u'abc' syntax: @_modify_str_or_docstring def u_format(s): From c19fc23b52363fe34802c80a8e46f7af9f4e78ec Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 1 Sep 2018 16:56:36 -0700 Subject: [PATCH 0246/3726] Update test_ipdoctest.py --- IPython/testing/plugin/test_ipdoctest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/IPython/testing/plugin/test_ipdoctest.py b/IPython/testing/plugin/test_ipdoctest.py index c14bc150761..d8f59916369 100644 --- a/IPython/testing/plugin/test_ipdoctest.py +++ b/IPython/testing/plugin/test_ipdoctest.py @@ -6,7 +6,6 @@ empty function call is counted as a test, which just inflates tests numbers artificially). """ -from IPython.utils.py3compat import doctest_refactor_print def doctest_simple(): """ipdoctest must handle simple inputs From 9983091e78d3c2cfc300531c65d45d294409cf4d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 4 Sep 2018 15:29:50 +0200 Subject: [PATCH 0247/3726] typo --- docs/source/whatsnew/pr/deprecations.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/whatsnew/pr/deprecations.rst b/docs/source/whatsnew/pr/deprecations.rst index 00fa5f9812a..1d10c267e44 100644 --- a/docs/source/whatsnew/pr/deprecations.rst +++ b/docs/source/whatsnew/pr/deprecations.rst @@ -1,5 +1,5 @@ -Depreations -=========== +Deprecations +============ A couple of unused function and methods have been deprecated and will be removed in future versions: From e53cc776805c9ba505f7febb6439946a73762fa8 Mon Sep 17 00:00:00 2001 From: oscar6echo Date: Wed, 5 Sep 2018 10:24:53 +0200 Subject: [PATCH 0248/3726] Add new methods in update_class() --- IPython/extensions/autoreload.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index dafae4c55c4..306bb8bd26a 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -269,7 +269,7 @@ def update_function(old, new): def update_class(old, new): """Replace stuff in the __dict__ of a class, and upgrade - method code objects""" + method code objects, and add new methods, if any""" for key in list(old.__dict__.keys()): old_obj = getattr(old, key) try: @@ -291,6 +291,13 @@ def update_class(old, new): except (AttributeError, TypeError): pass # skip non-writable attributes + for key in list(new.__dict__.keys()): + if key not in list(old.__dict__.keys()): + try: + setattr(old, key, getattr(new, key)) + except (AttributeError, TypeError): + pass # skip non-writable attributes + def update_property(old, new): """Replace get/set/del functions of a property""" From 4411772667974b04b20f994d91e62e0095f953fd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 12:13:08 +0200 Subject: [PATCH 0249/3726] add tests --- IPython/extensions/tests/test_autoreload.py | 57 +++++++++++++++++++++ IPython/testing/iptest.py | 6 +++ 2 files changed, 63 insertions(+) diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index 89c288b9ac5..ebeb9b3155d 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -151,6 +151,59 @@ class MyEnum(Enum): with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'): self.shell.run_code("pass") # trigger another reload + def test_reload_class_attributes(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module(textwrap.dedent(""" + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 22 + # self.toto = 33 + + def square(self): + print('compute square') + return self.a*self.a + """)) + self.shell.run_code("from %s import MyClass" % mod_name) + self.shell.run_code("c = MyClass(5)") + self.shell.run_code("c.square()") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.cube()") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.power(5)") + self.shell.run_code("c.b") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.toto") + + + self.write_file(mod_fn, textwrap.dedent(""" + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 11 + + def power(self, p): + print('compute power '+str(p)) + return self.a**p + """)) + + self.shell.run_code("d = MyClass(5)") + self.shell.run_code("d.power(5)") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.cube()") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.square(5)") + self.shell.run_code("c.b") + self.shell.run_code("c.a") + with nt.assert_raises(AttributeError): + self.shell.run_code("c.toto") + + + + + def _check_smoketest(self, use_aimport=True): """ @@ -340,3 +393,7 @@ def test_smoketest_aimport(self): def test_smoketest_autoreload(self): self._check_smoketest(use_aimport=False) + + + + diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 633cb724880..445e82657f0 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -385,6 +385,12 @@ def run_iptest(): if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'): monkeypatch_xunit() + arg1 = sys.argv[1] + if arg1.startswith('IPython/'): + if arg1.endswith('.py'): + arg1 = arg1[:-3] + sys.argv[1] = arg1.replace('/', '.') + arg1 = sys.argv[1] if arg1 in test_sections: section = test_sections[arg1] From 218daa3bccd9544bcb383c29e4d9df6324f97846 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 12:26:23 +0200 Subject: [PATCH 0250/3726] test both old and new classes --- IPython/extensions/tests/test_autoreload.py | 55 +++++++++++---------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index ebeb9b3155d..541b9529838 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -164,20 +164,26 @@ def __init__(self, a=10): def square(self): print('compute square') return self.a*self.a - """)) + """ + ) + ) self.shell.run_code("from %s import MyClass" % mod_name) - self.shell.run_code("c = MyClass(5)") - self.shell.run_code("c.square()") + self.shell.run_code("first = MyClass(5)") + self.shell.run_code("first.square()") with nt.assert_raises(AttributeError): - self.shell.run_code("c.cube()") + self.shell.run_code("first.cube()") with nt.assert_raises(AttributeError): - self.shell.run_code("c.power(5)") - self.shell.run_code("c.b") + self.shell.run_code("first.power(5)") + self.shell.run_code("first.b") with nt.assert_raises(AttributeError): - self.shell.run_code("c.toto") + self.shell.run_code("first.toto") + # remove square, add power - self.write_file(mod_fn, textwrap.dedent(""" + self.write_file( + mod_fn, + textwrap.dedent( + """ class MyClass: def __init__(self, a=10): @@ -187,23 +193,22 @@ def __init__(self, a=10): def power(self, p): print('compute power '+str(p)) return self.a**p - """)) - - self.shell.run_code("d = MyClass(5)") - self.shell.run_code("d.power(5)") - with nt.assert_raises(AttributeError): - self.shell.run_code("c.cube()") - with nt.assert_raises(AttributeError): - self.shell.run_code("c.square(5)") - self.shell.run_code("c.b") - self.shell.run_code("c.a") - with nt.assert_raises(AttributeError): - self.shell.run_code("c.toto") - - - - - + """ + ), + ) + + self.shell.run_code("second = MyClass(5)") + + for object_name in {'first', 'second'}: + self.shell.run_code("{object_name}.power(5)".format(object_name=object_name)) + with nt.assert_raises(AttributeError): + self.shell.run_code("{object_name}.cube()".format(object_name=object_name)) + with nt.assert_raises(AttributeError): + self.shell.run_code("{object_name}.square(5)".format(object_name=object_name)) + self.shell.run_code("{object_name}.b".format(object_name=object_name)) + self.shell.run_code("{object_name}.a".format(object_name=object_name)) + with nt.assert_raises(AttributeError): + self.shell.run_code("{object_name}.toto".format(object_name=object_name)) def _check_smoketest(self, use_aimport=True): """ From d96c198553b0579e9d2ba8eb196503e82e1b83d4 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 13:59:59 +0200 Subject: [PATCH 0251/3726] Add some extra metadata for PyPI (in the sidebar) Closes #11282 --- setupbase.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/setupbase.py b/setupbase.py index 23b3c895771..347c525ed0d 100644 --- a/setupbase.py +++ b/setupbase.py @@ -36,10 +36,6 @@ pjoin = os.path.join repo_root = os.path.dirname(os.path.abspath(__file__)) -def oscmd(s): - print(">", s) - os.system(s) - def execfile(fname, globs, locs=None): locs = locs or globs exec(compile(open(fname).read(), fname, "exec"), globs, locs) @@ -78,6 +74,12 @@ def file_doesnt_endwith(test,endings): keywords = keywords, classifiers = classifiers, cmdclass = {'install_data': install_data_ext}, + project_urls={ + 'Documentation': 'https://ipython.readthedocs.io/', + 'Funding' : 'https://numfocus.org/', + 'Source' : 'https://github.com/ipython/ipython', + 'Tracker' : 'https://github.com/ipython/ipython/issues', + } ) From 761e62c0032c6ae779dc6b2d1938c120975ee804 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 14:03:47 +0200 Subject: [PATCH 0252/3726] remove unused utils function in setupbase.py --- setupbase.py | 61 ---------------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/setupbase.py b/setupbase.py index 23b3c895771..fb9624f0e75 100644 --- a/setupbase.py +++ b/setupbase.py @@ -152,43 +152,6 @@ def run(self): # Find data files #--------------------------------------------------------------------------- -def make_dir_struct(tag,base,out_base): - """Make the directory structure of all files below a starting dir. - - This is just a convenience routine to help build a nested directory - hierarchy because distutils is too stupid to do this by itself. - - XXX - this needs a proper docstring! - """ - - # we'll use these a lot below - lbase = len(base) - pathsep = os.path.sep - lpathsep = len(pathsep) - - out = [] - for (dirpath,dirnames,filenames) in os.walk(base): - # we need to strip out the dirpath from the base to map it to the - # output (installation) path. This requires possibly stripping the - # path separator, because otherwise pjoin will not work correctly - # (pjoin('foo/','/bar') returns '/bar'). - - dp_eff = dirpath[lbase:] - if dp_eff.startswith(pathsep): - dp_eff = dp_eff[lpathsep:] - # The output path must be anchored at the out_base marker - out_path = pjoin(out_base,dp_eff) - # Now we can generate the final filenames. Since os.walk only produces - # filenames, we must join back with the dirpath to get full valid file - # paths: - pfiles = [pjoin(dirpath,f) for f in filenames] - # Finally, generate the entry we need, which is a pari of (output - # path, files) for use as a data_files parameter in install_data. - out.append((out_path, pfiles)) - - return out - - def find_data_files(): """ Find IPython's data_files. @@ -210,30 +173,6 @@ def find_data_files(): return data_files -def make_man_update_target(manpage): - """Return a target_update-compliant tuple for the given manpage. - - Parameters - ---------- - manpage : string - Name of the manpage, must include the section number (trailing number). - - Example - ------- - - >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE - ('docs/man/ipython.1.gz', - ['docs/man/ipython.1'], - 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz') - """ - man_dir = pjoin('docs', 'man') - manpage_gz = manpage + '.gz' - manpath = pjoin(man_dir, manpage) - manpath_gz = pjoin(man_dir, manpage_gz) - gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" % - locals() ) - return (manpath_gz, [manpath], gz_cmd) - # The two functions below are copied from IPython.utils.path, so we don't need # to import IPython during setup, which fails on Python 3. From 85e4bd2e1e6bf35a2c818d553667cedc6511ac08 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 20:09:48 +0200 Subject: [PATCH 0253/3726] Switch scripts cell magic default to raise if error happens. Close #11198 --- IPython/core/magics/script.py | 2 +- docs/source/whatsnew/pr/bash_raise_default.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/source/whatsnew/pr/bash_raise_default.rst diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index d84b27148bd..8b7f6f94e09 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -55,7 +55,7 @@ def script_args(f): """ ), magic_arguments.argument( - '--raise-error', action="store_true", + '--no-raise-error', action="store_false", dest='raise_error', help="""Whether you should raise an error message in addition to a stream on stderr if you get a nonzero exit code. """ diff --git a/docs/source/whatsnew/pr/bash_raise_default.rst b/docs/source/whatsnew/pr/bash_raise_default.rst new file mode 100644 index 00000000000..dc2a14e0993 --- /dev/null +++ b/docs/source/whatsnew/pr/bash_raise_default.rst @@ -0,0 +1,4 @@ +The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magic no raise by +default if the return code of the given code is non-zero (this halting execution +of further cells in a notebook). The behavior can be disable by passing the +``--no-raise-error`` flag. From b09fff3235e07056c9d64defa538d0fa275ededd Mon Sep 17 00:00:00 2001 From: oscar6echo Date: Thu, 6 Sep 2018 21:24:05 +0200 Subject: [PATCH 0254/3726] Fix inconsequential typo in test --- IPython/extensions/tests/test_autoreload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index 541b9529838..a942c5ebc15 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -204,7 +204,7 @@ def power(self, p): with nt.assert_raises(AttributeError): self.shell.run_code("{object_name}.cube()".format(object_name=object_name)) with nt.assert_raises(AttributeError): - self.shell.run_code("{object_name}.square(5)".format(object_name=object_name)) + self.shell.run_code("{object_name}.square()".format(object_name=object_name)) self.shell.run_code("{object_name}.b".format(object_name=object_name)) self.shell.run_code("{object_name}.a".format(object_name=object_name)) with nt.assert_raises(AttributeError): From f28c85f327299a5faeb69660f335238e98ab007a Mon Sep 17 00:00:00 2001 From: oscar6echo Date: Thu, 6 Sep 2018 21:48:13 +0200 Subject: [PATCH 0255/3726] Add file in whatsnew/pr --- docs/source/whatsnew/pr/improve-autoreload.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/source/whatsnew/pr/improve-autoreload.md diff --git a/docs/source/whatsnew/pr/improve-autoreload.md b/docs/source/whatsnew/pr/improve-autoreload.md new file mode 100644 index 00000000000..70622991e67 --- /dev/null +++ b/docs/source/whatsnew/pr/improve-autoreload.md @@ -0,0 +1,30 @@ +magic `%autoreload 2` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated. + +This new feature helps dual environement development - Jupyter+IDE - where the code gradually moves from notebook cells to package files, as it gets structured. + +**Example**: An instance of the class `MyClass` will be able to access the method `cube()` after it is uncommented and the file `file1.py` saved on disk. + +````python +# notebook + +from mymodule import MyClass +first = MyClass(5) +```` + +````python +# mymodule/file1.py + +class MyClass: + + def __init__(self, a=10): + self.a = a + + def square(self): + print('compute square') + return self.a*self.a + + # def cube(self): + # print('compute cube') + # return self.a*self.a*self.a +```` + From 85db7036a93534413128e7f196635d1e99470f60 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 22:04:57 +0200 Subject: [PATCH 0256/3726] Tool to go around sphinx limitation during developpement --- tools/fixup_whats_new_pr.py | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tools/fixup_whats_new_pr.py diff --git a/tools/fixup_whats_new_pr.py b/tools/fixup_whats_new_pr.py new file mode 100644 index 00000000000..bfba754cfca --- /dev/null +++ b/tools/fixup_whats_new_pr.py @@ -0,0 +1,43 @@ +""" +This tool is used during CI testing to make sure sphinx raise no error. + +During development, we like to have whatsnew/pr/*.rst documents to track +individual new features. Unfortunately they other either: + - have no title (sphinx complains) + - are not included in any toctree (sphinx complain) + +This fix-them up by "inventing" a title, before building the docs. At release +time, these title and files will anyway be rewritten into the actual release +notes. +""" + +import glob + + +def main(): + folder = 'docs/source/whatsnew/pr/' + assert folder.endswith('/') + files = glob.glob(folder+'*.rst') + print(files) + + for filename in files: + print('Adding pseudo-title to:', filename) + title = filename[:-4].split('/')[-1].replace('-', ' ').capitalize() + + with open(filename) as f: + data = f.read() + if data and data.splitlines()[1].startswith('='): + continue + + with open(filename, 'w') as f: + f.write(title+'\n') + f.write('='* len(title)+'\n\n') + f.write(data) + +if __name__ == '__main__': + main() + + + + + From e4c4b838efaa02f38a9757180e6d1750dcdfde6e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 6 Sep 2018 22:32:53 +0200 Subject: [PATCH 0257/3726] run doc fixing tool on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 73d419f90a1..fc632dd978e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ script: - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then pip install -r docs/requirements.txt + python tools/fixup_whats_new_pr.py make -C docs/ html SPHINXOPTS="-W" fi after_success: From 7264f10677ddea079860dc801901c5b8afbf8656 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Fri, 7 Sep 2018 01:03:18 -0700 Subject: [PATCH 0258/3726] Pass inputhook to prompt_toolkit --- IPython/terminal/interactiveshell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 2305e0b4eff..35cb0697849 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -367,6 +367,7 @@ def get_message(): processor=HighlightMatchingBracketProcessor(chars='[](){}'), filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & Condition(lambda: self.highlight_matching_brackets))], + 'inputhook': self.inputhook, } def prompt_for_code(self): From db9663979e18e2eecd276ce7139d9fea74c8201c Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 9 Jan 2018 12:17:20 +0100 Subject: [PATCH 0259/3726] Disable qt inputhook backend when DISPLAY is not set on linux, because the qt lib does not handle this correct --- IPython/terminal/pt_inputhooks/qt.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index 0de9c3db1d2..5b05ac93bc0 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -1,15 +1,32 @@ import sys +import os from IPython.external.qt_for_kernel import QtCore, QtGui # If we create a QApplication, keep a reference to it so that it doesn't get # garbage collected. _appref = None - +_already_warned = False def inputhook(context): global _appref app = QtCore.QCoreApplication.instance() if not app: + if sys.platform == 'linux': + try: + os.environ['DISPLAY'] # DISPLAY is set + assert os.environ['DISPLAY'] != '' # DISPLAY is not empty + except Exception: + import warnings + global _already_warned + if not _already_warned: + _already_warned = True + warnings.warn( + 'The DISPLAY enviroment variable is not set or empty ' + 'and qt requires this enviroment variable. ' + 'Deaktivate qt code.\n' + 'Backend: {}'.format(QtGui) + ) + return _appref = app = QtGui.QApplication([" "]) event_loop = QtCore.QEventLoop(app) From 2da8381fd6b8ffec6c66ccfabeacc3d22adb95c2 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 6 Mar 2018 10:22:38 +0100 Subject: [PATCH 0260/3726] apply review comments --- IPython/terminal/pt_inputhooks/qt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index 5b05ac93bc0..ae32dca5983 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -22,9 +22,8 @@ def inputhook(context): _already_warned = True warnings.warn( 'The DISPLAY enviroment variable is not set or empty ' - 'and qt requires this enviroment variable. ' - 'Deaktivate qt code.\n' - 'Backend: {}'.format(QtGui) + 'and Qt5 requires this enviroment variable. ' + 'Deaktivate Qt5 code.' ) return _appref = app = QtGui.QApplication([" "]) From 100e00891db22e421eb46b84131bcbeb3da69a72 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 6 Mar 2018 10:28:11 +0100 Subject: [PATCH 0261/3726] type --- IPython/terminal/pt_inputhooks/qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index ae32dca5983..58a53778316 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -23,7 +23,7 @@ def inputhook(context): warnings.warn( 'The DISPLAY enviroment variable is not set or empty ' 'and Qt5 requires this enviroment variable. ' - 'Deaktivate Qt5 code.' + 'Deactivate Qt5 code.' ) return _appref = app = QtGui.QApplication([" "]) From acc5655546dd6b439d8d38697e4bbad19b37137e Mon Sep 17 00:00:00 2001 From: Christoph Date: Thu, 8 Mar 2018 09:53:14 +0100 Subject: [PATCH 0262/3726] check also WAYLAND_DISPLAY --- IPython/terminal/pt_inputhooks/qt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index 58a53778316..d2e23ab6d1f 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -13,8 +13,8 @@ def inputhook(context): if not app: if sys.platform == 'linux': try: - os.environ['DISPLAY'] # DISPLAY is set - assert os.environ['DISPLAY'] != '' # DISPLAY is not empty + # DISPLAY or WAYLAND_DISPLAY is set and not empty + assert os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY') except Exception: import warnings global _already_warned From 85356b9d81cf6fe36f9f0667234f906afb676678 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 27 Apr 2018 14:25:06 +0200 Subject: [PATCH 0263/3726] apply comments --- IPython/terminal/pt_inputhooks/qt.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index d2e23ab6d1f..7395ac39ebe 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -12,18 +12,16 @@ def inputhook(context): app = QtCore.QCoreApplication.instance() if not app: if sys.platform == 'linux': - try: - # DISPLAY or WAYLAND_DISPLAY is set and not empty - assert os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY') - except Exception: + if not os.environ.get('DISPLAY') \ + and not os.environ.get('WAYLAND_DISPLAY'): import warnings global _already_warned if not _already_warned: _already_warned = True warnings.warn( - 'The DISPLAY enviroment variable is not set or empty ' - 'and Qt5 requires this enviroment variable. ' - 'Deactivate Qt5 code.' + 'The DISPLAY or WAYLAND_DISPLAY enviroment variable is ' + 'not set or empty and Qt5 requires this enviroment ' + 'variable. Deactivate Qt5 code.' ) return _appref = app = QtGui.QApplication([" "]) From 23eb6b09919faf528ed7ffc94d871e7344c4c80d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Sep 2018 13:51:09 +0200 Subject: [PATCH 0264/3726] Start to updates the what's new / changelog. Also improve the documentation process to auto-list all the files in docs/source/wn/pr with daily readthedocs builds --- docs/source/conf.py | 3 +- docs/source/whatsnew/development.rst | 16 +- docs/source/whatsnew/index.rst | 1 + .../whatsnew/pr/antigravity-feature.rst | 5 + docs/source/whatsnew/pr/await-repl.rst | 99 -------- docs/source/whatsnew/pr/deprecations.rst | 10 - docs/source/whatsnew/pr/improve-autoreload.md | 30 --- .../pr/incompat-inputtransformer2.rst | 3 - .../pr/incompat-switching-to-perl.rst | 1 + docs/source/whatsnew/pr/magic-run-bug-fix.md | 1 - docs/source/whatsnew/pr/re-autoindent.rst | 2 - docs/source/whatsnew/version7.rst | 214 ++++++++++++++++++ tools/fixup_whats_new_pr.py | 7 +- 13 files changed, 228 insertions(+), 164 deletions(-) delete mode 100644 docs/source/whatsnew/pr/await-repl.rst delete mode 100644 docs/source/whatsnew/pr/deprecations.rst delete mode 100644 docs/source/whatsnew/pr/improve-autoreload.md delete mode 100644 docs/source/whatsnew/pr/incompat-inputtransformer2.rst delete mode 100644 docs/source/whatsnew/pr/magic-run-bug-fix.md delete mode 100644 docs/source/whatsnew/pr/re-autoindent.rst create mode 100644 docs/source/whatsnew/version7.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 7d99ebd36ae..11a3cb973d2 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,8 +143,7 @@ def is_stable(extra): # Exclude these glob-style patterns when looking for source files. They are # relative to the source/ directory. -exclude_patterns = ['whatsnew/pr/antigravity-feature.*', - 'whatsnew/pr/incompat-switching-to-perl.*'] +exclude_patterns = [] # If true, '()' will be appended to :func: etc. cross-reference text. diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index b8b2f004fb7..532ba742778 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -18,24 +18,10 @@ Need to be updated: .. toctree:: :maxdepth: 2 - :glob: + :glob: pr/* -IPython 6 feature a major improvement in the completion machinery which is now -capable of completing non-executed code. It is also the first version of IPython -to stop compatibility with Python 2, which is still supported on the bugfix only -5.x branch. Read below to have a non-exhaustive list of new features. - -Make sure you have pip > 9.0 before upgrading. -You should be able to update by using: - -.. code:: - - pip install ipython --upgrade - - - .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 3ea26c3fc35..ef11789a93d 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -21,6 +21,7 @@ development work they do here in a user friendly format. :maxdepth: 1 development + version7 version6 github-stats-6 version5 diff --git a/docs/source/whatsnew/pr/antigravity-feature.rst b/docs/source/whatsnew/pr/antigravity-feature.rst index e69de29bb2d..a53f8c7417c 100644 --- a/docs/source/whatsnew/pr/antigravity-feature.rst +++ b/docs/source/whatsnew/pr/antigravity-feature.rst @@ -0,0 +1,5 @@ +Antigravity feature +=================== + +Example new antigravity feature. Try ``import antigravity`` in a Python 3 +console. diff --git a/docs/source/whatsnew/pr/await-repl.rst b/docs/source/whatsnew/pr/await-repl.rst deleted file mode 100644 index d61dc2e848c..00000000000 --- a/docs/source/whatsnew/pr/await-repl.rst +++ /dev/null @@ -1,99 +0,0 @@ -Autowait: Asynchronous REPL ---------------------------- - -Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await -code at top level, you should not need to access an event loop or runner -yourself. To know more read the :ref:`autoawait` section of our docs, see -:ghpull:`11265` or try the following code:: - - Python 3.6.0 - Type 'copyright', 'credits' or 'license' for more information - IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help. - - In [1]: import aiohttp - ...: result = aiohttp.get('https://api.github.com') - - In [2]: response = await result - - - In [3]: await response.json() - Out[3]: - {'authorizations_url': 'https://api.github.com/authorizations', - 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', - ... - } - -.. note:: - - Async integration is experimental code, behavior may change or be removed - between Python and IPython versions without warnings. - -Integration is by default with `asyncio`, but other libraries can be configured, -like ``curio`` or ``trio``, to improve concurrency in the REPL:: - - In [1]: %autoawait trio - - In [2]: import trio - - In [3]: async def child(i): - ...: print(" child %s goes to sleep"%i) - ...: await trio.sleep(2) - ...: print(" child %s wakes up"%i) - - In [4]: print('parent start') - ...: async with trio.open_nursery() as n: - ...: for i in range(3): - ...: n.spawn(child, i) - ...: print('parent end') - parent start - child 2 goes to sleep - child 0 goes to sleep - child 1 goes to sleep - - child 2 wakes up - child 1 wakes up - child 0 wakes up - parent end - -See :ref:`autoawait` for more information. - - -Asynchronous code in a Notebook interface or any other frontend using the -Jupyter Protocol will need further updates of the IPykernel package. - -Non-Asynchronous code ---------------------- - -As the internal API of IPython are now asynchronous, IPython need to run under -an even loop. In order to allow many workflow, (like using the ``%run`` magic, -or copy_pasting code that explicitly starts/stop event loop), when top-level code -is detected as not being asynchronous, IPython code is advanced via a -pseudo-synchronous runner, and will not may not advance pending tasks. - -Change to Nested Embed ----------------------- - -The introduction of the ability to run async code had some effect on the -``IPython.embed()`` API. By default embed will not allow you to run asynchronous -code unless a event loop is specified. - -Effects on Magics ------------------ - -Some magics will not work with Async, and will need updates. Contribution -welcome. - -Expected Future changes ------------------------ - -We expect more internal but public IPython function to become ``async``, and -will likely end up having a persisting event loop while IPython is running. - -Thanks ------- - -This took more than a year in the making, and the code was rebased a number of -time leading to commit authorship that may have been lost in the final -Pull-Request. Huge thanks to many people for contribution, discussion, code, -documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor, -minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other. diff --git a/docs/source/whatsnew/pr/deprecations.rst b/docs/source/whatsnew/pr/deprecations.rst deleted file mode 100644 index 1d10c267e44..00000000000 --- a/docs/source/whatsnew/pr/deprecations.rst +++ /dev/null @@ -1,10 +0,0 @@ -Deprecations -============ - -A couple of unused function and methods have been deprecated and will be removed -in future versions: - - - ``IPython.utils.io.raw_print_err`` - - ``IPython.utils.io.raw_print`` - - diff --git a/docs/source/whatsnew/pr/improve-autoreload.md b/docs/source/whatsnew/pr/improve-autoreload.md deleted file mode 100644 index 70622991e67..00000000000 --- a/docs/source/whatsnew/pr/improve-autoreload.md +++ /dev/null @@ -1,30 +0,0 @@ -magic `%autoreload 2` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated. - -This new feature helps dual environement development - Jupyter+IDE - where the code gradually moves from notebook cells to package files, as it gets structured. - -**Example**: An instance of the class `MyClass` will be able to access the method `cube()` after it is uncommented and the file `file1.py` saved on disk. - -````python -# notebook - -from mymodule import MyClass -first = MyClass(5) -```` - -````python -# mymodule/file1.py - -class MyClass: - - def __init__(self, a=10): - self.a = a - - def square(self): - print('compute square') - return self.a*self.a - - # def cube(self): - # print('compute cube') - # return self.a*self.a*self.a -```` - diff --git a/docs/source/whatsnew/pr/incompat-inputtransformer2.rst b/docs/source/whatsnew/pr/incompat-inputtransformer2.rst deleted file mode 100644 index ee3bd56e6ef..00000000000 --- a/docs/source/whatsnew/pr/incompat-inputtransformer2.rst +++ /dev/null @@ -1,3 +0,0 @@ -* The API for transforming input before it is parsed as Python code has been - completely redesigned, and any custom input transformations will need to be - rewritten. See :doc:`/config/inputtransforms` for details of the new API. diff --git a/docs/source/whatsnew/pr/incompat-switching-to-perl.rst b/docs/source/whatsnew/pr/incompat-switching-to-perl.rst index e69de29bb2d..35f73dffe1c 100644 --- a/docs/source/whatsnew/pr/incompat-switching-to-perl.rst +++ b/docs/source/whatsnew/pr/incompat-switching-to-perl.rst @@ -0,0 +1 @@ +Starting with IPython 42, only perl code execution is allowed. See :ghpull:`42` diff --git a/docs/source/whatsnew/pr/magic-run-bug-fix.md b/docs/source/whatsnew/pr/magic-run-bug-fix.md deleted file mode 100644 index bd0ab76f099..00000000000 --- a/docs/source/whatsnew/pr/magic-run-bug-fix.md +++ /dev/null @@ -1 +0,0 @@ -Make ``%run -n -i ...`` work correctly. Earlier, if ``%run`` was passed both arguments, ``-n`` would be silently ignored. diff --git a/docs/source/whatsnew/pr/re-autoindent.rst b/docs/source/whatsnew/pr/re-autoindent.rst deleted file mode 100644 index a670d5ff98d..00000000000 --- a/docs/source/whatsnew/pr/re-autoindent.rst +++ /dev/null @@ -1,2 +0,0 @@ -The autoindent feature that was deprecated in 5.x was re-enabled and -un-deprecated in :ghpull:`11257` diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst new file mode 100644 index 00000000000..b20cfab0438 --- /dev/null +++ b/docs/source/whatsnew/version7.rst @@ -0,0 +1,214 @@ +============ + 7.x Series +============ + +.. _whatsnew700: + +IPython 7.0.0 +============= + +.. warning:: + + IPython 7.0 is currently in Beta, Feedback on API/changes and + addition/updates to this cahngelog are welcomed. + +Released .... ...., 2017 + +IPython 7 include major features improvement as you can read in the following +changelog. This is also the second major version of IPython to stop support only +Python 3 – starting at Python 3.4. Python 2 is still still community supported +on the bugfix only 5.x branch, but we remind you that Python 2 EOL is Jan 1st +2020. + +We were able to backport bug fixes to the 5.x branch thanks to our backport bot which +backported more than `70 Pull-Requests +`_, but there are still many PRs that required manually work, and this is an area of the project were you can easily contribute by looking for `PRs still needed backport`_ + +IPython 6.x branch will likely not see any further release unless we critical +bugs are found. + +Make sure you have pip > 9.0 before upgrading. You should be able to update by simply runngin + +.. code:: + + pip install ipython --upgrade + +Or if you have conda installed: + +.. code:: + + conda install ipython + + + +Prompt Toolkit 2.0 +------------------ + +IPython 7.0+ now use ``prompt_toolkit 2.0``, if you still need to use earlier +``prompt_toolkit`` version you may need to pin IPython to ``<7.0``. + +Autowait: Asynchronous REPL +--------------------------- + +Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await +code at top level, you should not need to access an event loop or runner +yourself. To know more read the :ref:`autoawait` section of our docs, see +:ghpull:`11265` or try the following code:: + + Python 3.6.0 + Type 'copyright', 'credits' or 'license' for more information + IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help. + + In [1]: import aiohttp + ...: result = aiohttp.get('https://api.github.com') + + In [2]: response = await result + + + In [3]: await response.json() + Out[3]: + {'authorizations_url': 'https://api.github.com/authorizations', + 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}', + ... + } + +.. note:: + + Async integration is experimental code, behavior may change or be removed + between Python and IPython versions without warnings. + +Integration is by default with `asyncio`, but other libraries can be configured, +like ``curio`` or ``trio``, to improve concurrency in the REPL:: + + In [1]: %autoawait trio + + In [2]: import trio + + In [3]: async def child(i): + ...: print(" child %s goes to sleep"%i) + ...: await trio.sleep(2) + ...: print(" child %s wakes up"%i) + + In [4]: print('parent start') + ...: async with trio.open_nursery() as n: + ...: for i in range(3): + ...: n.spawn(child, i) + ...: print('parent end') + parent start + child 2 goes to sleep + child 0 goes to sleep + child 1 goes to sleep + + child 2 wakes up + child 1 wakes up + child 0 wakes up + parent end + +See :ref:`autoawait` for more information. + + +Asynchronous code in a Notebook interface or any other frontend using the +Jupyter Protocol will need further updates of the IPykernel package. + +Non-Asynchronous code +~~~~~~~~~~~~~~~~~~~~~ + +As the internal API of IPython are now asynchronous, IPython need to run under +an even loop. In order to allow many workflow, (like using the ``%run`` magic, +or copy_pasting code that explicitly starts/stop event loop), when top-level code +is detected as not being asynchronous, IPython code is advanced via a +pseudo-synchronous runner, and will not may not advance pending tasks. + +Change to Nested Embed +~~~~~~~~~~~~~~~~~~~~~~ + +The introduction of the ability to run async code had some effect on the +``IPython.embed()`` API. By default embed will not allow you to run asynchronous +code unless a event loop is specified. + +Effects on Magics +~~~~~~~~~~~~~~~~~ + +Some magics will not work with Async, and will need updates. Contribution +welcome. + +Expected Future changes +~~~~~~~~~~~~~~~~~~~~~~~ + +We expect more internal but public IPython function to become ``async``, and +will likely end up having a persisting event loop while IPython is running. + +Thanks +~~~~~~ + +This took more than a year in the making, and the code was rebased a number of +time leading to commit authorship that may have been lost in the final +Pull-Request. Huge thanks to many people for contribution, discussion, code, +documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor, +minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other. + + +Autoreload Improvment +--------------------- + +The magic ``%autoreload 2`` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated. + +This new feature helps dual environment development - Jupyter+IDE - where the code gradually moves from notebook cells to package files, as it gets structured. + +**Example**: An instance of the class `MyClass` will be able to access the method `cube()` after it is uncommented and the file `file1.py` saved on disk. + + +..code:: + + # notebook + + from mymodule import MyClass + first = MyClass(5) + +.. code:: + # mymodule/file1.py + + class MyClass: + + def __init__(self, a=10): + self.a = a + + def square(self): + print('compute square') + return self.a*self.a + + # def cube(self): + # print('compute cube') + # return self.a*self.a*self.a + + + + +Misc +---- + +The autoindent feature that was deprecated in 5.x was re-enabled and +un-deprecated in :ghpull:`11257` + +Make ``%run -n -i ...`` work correctly. Earlier, if ``%run`` was passed both arguments, ``-n`` would be silently ignored. See :ghpull:`10308` + + + + + +Deprecations +------------ + +A couple of unused function and methods have been deprecated and will be removed +in future versions: + + - ``IPython.utils.io.raw_print_err`` + - ``IPython.utils.io.raw_print`` + + +Backwards incompatible changes +------------------------------ + +* The API for transforming input before it is parsed as Python code has been + completely redesigned, and any custom input transformations will need to be + rewritten. See :doc:`/config/inputtransforms` for details of the new API. diff --git a/tools/fixup_whats_new_pr.py b/tools/fixup_whats_new_pr.py index bfba754cfca..057b2e48cbb 100644 --- a/tools/fixup_whats_new_pr.py +++ b/tools/fixup_whats_new_pr.py @@ -26,8 +26,11 @@ def main(): with open(filename) as f: data = f.read() - if data and data.splitlines()[1].startswith('='): - continue + try: + if data and data.splitlines()[1].startswith('='): + continue + except IndexError: + pass with open(filename, 'w') as f: f.write(title+'\n') From 05a6573b4ae8d80838e3ceb5d862ada07187ee7f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Sep 2018 14:31:28 +0200 Subject: [PATCH 0265/3726] update release process --- docs/source/coredev/index.rst | 15 ++++++++------- docs/source/whatsnew/index.rst | 18 +++++++++++++++++- docs/source/whatsnew/version7.rst | 3 ++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/source/coredev/index.rst b/docs/source/coredev/index.rst index 5cb5b178a10..67e2c01437c 100644 --- a/docs/source/coredev/index.rst +++ b/docs/source/coredev/index.rst @@ -19,18 +19,17 @@ need to be backported to an earlier release; then it should be tagged with the correct ``milestone``. If you tag a pull request with a milestone **before** merging the pull request, -and the base ref is `master`, then our backport bot should automatically create +and the base ref is ``master``, then our backport bot should automatically create a corresponding pull-request that backport on the correct branch. -If you are an admin on the IPython repository you can also just mention the +If you have write access to the IPython repository you can also just mention the **backport bot** to do the work for you. The bot is evolving so instructions may be different. At the time of this writing you can use:: - @meeseeksdev[bot] backport [to ] + @meeseeksdev[bot] backport [to] The bot will attempt to backport the current pull-request and issue a PR if -possible. If the milestone is set on the issue you can omit the branch to -backport to. +possible. .. note:: @@ -149,8 +148,10 @@ If a major release: - Edit ``docs/source/whatsnew/index.rst`` to list the new ``github-stats-X`` file you just created. - - Remove temporarily the first entry called ``development`` (you'll need to - add it back after release). + - You do not need to temporarily remove the first entry called + ``development``, nor re-add it after the release, it will automatically be + hidden when releasing a stable version of IPython (if ``_version_extra`` + in ``release.py`` is an empty string. Make sure that the stats file has a header or it won't be rendered in the final documentation. diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index ef11789a93d..6c638d836d4 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -12,6 +12,23 @@ What's new in IPython ===================== +.. this will appear in the docs if we are nto releasing a versin (ie is +.. _version_extra in release.py is empty stringA + +.. only:: ipydev + + Developpement version in-progress features: + + .. toctree:: + development + +.. this make a hidden toctree that avoid sphinx to complain about documents +.. included nowhere when building docs for stable +.. only:: ipystable + .. toctree:: + :hidden: + development + This section documents the changes that have been made in various versions of IPython. Users should consult these pages to learn about new features, bug fixes and backwards incompatibilities. Developers should summarize the @@ -20,7 +37,6 @@ development work they do here in a user friendly format. .. toctree:: :maxdepth: 1 - development version7 version6 github-stats-6 diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index b20cfab0438..f53e3415307 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -22,7 +22,7 @@ on the bugfix only 5.x branch, but we remind you that Python 2 EOL is Jan 1st We were able to backport bug fixes to the 5.x branch thanks to our backport bot which backported more than `70 Pull-Requests -`_, but there are still many PRs that required manually work, and this is an area of the project were you can easily contribute by looking for `PRs still needed backport`_ +`_, but there are still many PRs that required manually work, and this is an area of the project were you can easily contribute by looking for `PRs still needed backport `_ IPython 6.x branch will likely not see any further release unless we critical bugs are found. @@ -166,6 +166,7 @@ This new feature helps dual environment development - Jupyter+IDE - where the co first = MyClass(5) .. code:: + # mymodule/file1.py class MyClass: From 64e14897ee532b995a8eaf40829cc380dc453ca3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 13:22:35 +0200 Subject: [PATCH 0266/3726] make `run_cell_async` a regular coroutine separate "should it be async" to a `should_run_async` public method --- IPython/core/interactiveshell.py | 70 ++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 5f8d3d20be0..c2b57b0642e 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2830,32 +2830,52 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures shell_futures=shell_futures, ) - # run_cell_async is async, but may not actually need and eventloop. + # run_cell_async is async, but may not actually need an eventloop. # when this is the case, we want to run it using the pseudo_sync_runner # so that code can invoke eventloops (for example via the %run , and # `%paste` magic. + if self.should_run_async(raw_cell): + runner = self.loop_runner + else: + runner = _pseudo_sync_runner + try: - interactivity = coro.send(None) - except StopIteration as exc: - return exc.value + return runner(coro) + except Exception as e: + info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) + result = ExecutionResult(info) + result.error_in_exec = e + self.showtraceback(running_compiled_code=True) + return result + return - # if code was not async, sending `None` was actually executing the code. - if isinstance(interactivity, ExecutionResult): - return interactivity + def should_run_async(self, raw_cell: str) -> bool: + """Return whether a cell should be run asynchronously via a coroutine runner - if interactivity == 'async': - try: - return self.loop_runner(coro) - except Exception as e: - info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) - result = ExecutionResult(info) - result.error_in_exec = e - self.showtraceback(running_compiled_code=True) - return result - return _pseudo_sync_runner(coro) + Parameters + ---------- + raw_cell: str + The code to be executed + + Returns + ------- + result: bool + Whether the code needs to be run with a coroutine runner or not + + .. versionadded: 7.0 + """ + if not self.autoawait: + return False + try: + cell = self.transform_cell(raw_cell) + except Exception: + # any exception during transform will be raised + # prior to execution + return False + return _should_be_async(cell) @asyncio.coroutine - def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult: + def run_cell_async(self, raw_cell: str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult: """Run a complete IPython cell asynchronously. Parameters @@ -2878,6 +2898,8 @@ def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_ Returns ------- result : :class:`ExecutionResult` + + .. versionadded: 7.0 """ info = ExecutionInfo( raw_cell, store_history, silent, shell_futures) @@ -2910,13 +2932,13 @@ def error_before_exec(value): # prefilter_manager) raises an exception, we store it in this variable # so that we can display the error after logging the input and storing # it in the history. - preprocessing_exc_tuple = None try: - # Static input transformations cell = self.transform_cell(raw_cell) except Exception: preprocessing_exc_tuple = sys.exc_info() cell = raw_cell # cell has to exist so it can be stored/logged + else: + preprocessing_exc_tuple = None # Store raw and processed history if store_history: @@ -2991,12 +3013,10 @@ def error_before_exec(value): interactivity = "none" if silent else self.ast_node_interactivity if _run_async: interactivity = 'async' - # yield interactivity so let run_cell decide whether to use - # an async loop_runner - yield interactivity + has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name, interactivity=interactivity, compiler=compiler, result=result) - + self.last_execution_succeeded = not has_raised self.last_execution_result = result @@ -3042,7 +3062,7 @@ def transform_cell(self, raw_cell): cell = ''.join(lines) return cell - + def transform_ast(self, node): """Apply the AST transformations from self.ast_transformers From c41cb1fad76bfe59a2a7c8ae3a733362b0f45955 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 14:43:13 +0200 Subject: [PATCH 0267/3726] exercise calling run_cell_async as a coroutine --- IPython/core/tests/test_interactiveshell.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index b9eae2535ea..39fa41bd4df 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -9,6 +9,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio import ast import os import signal @@ -24,6 +25,7 @@ from IPython.core.error import InputRejected from IPython.core.inputtransformer import InputTransformer +from IPython.core import interactiveshell from IPython.testing.decorators import ( skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist, ) @@ -928,3 +930,19 @@ def test_custom_exc_count(): ip.set_custom_exc((), None) nt.assert_equal(hook.call_count, 1) nt.assert_equal(ip.execution_count, before + 1) + + +def test_run_cell_async(): + loop = asyncio.get_event_loop() + ip.run_cell("import asyncio") + coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5") + assert asyncio.iscoroutine(coro) + result = loop.run_until_complete(coro) + assert isinstance(result, interactiveshell.ExecutionResult) + assert result.result == 5 + + +def test_should_run_async(): + assert not ip.should_run_async("a = 5") + assert ip.should_run_async("await x") + assert ip.should_run_async("import asyncio; await asyncio.sleep(1)") From 1a450179ae9f36a22c91bdaa4da500c9e3d650c6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Sep 2018 14:44:03 +0200 Subject: [PATCH 0268/3726] fix comments --- docs/source/whatsnew/index.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 6c638d836d4..bc606080f7d 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -12,8 +12,9 @@ What's new in IPython ===================== -.. this will appear in the docs if we are nto releasing a versin (ie is -.. _version_extra in release.py is empty stringA +.. + this will appear in the docs if we are nto releasing a versin (ie is + `_version_extra` in release.py is empty stringA .. only:: ipydev @@ -22,8 +23,9 @@ What's new in IPython .. toctree:: development -.. this make a hidden toctree that avoid sphinx to complain about documents -.. included nowhere when building docs for stable +.. + this make a hidden toctree that avoid sphinx to complain about documents + included nowhere when building docs for stable .. only:: ipystable .. toctree:: :hidden: From 33ab194409bdf3a0fb18bdd8a9b90e0f23c259cc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Sep 2018 15:13:02 +0200 Subject: [PATCH 0269/3726] build error on traivs --- docs/source/whatsnew/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index bc606080f7d..51aa0fe1aa4 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -26,7 +26,9 @@ What's new in IPython .. this make a hidden toctree that avoid sphinx to complain about documents included nowhere when building docs for stable + .. only:: ipystable + .. toctree:: :hidden: development From 89601ae711d291fc02aba42ce80110f719eeaa8e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Sep 2018 15:38:34 +0200 Subject: [PATCH 0270/3726] Add debug and fix build hopefully --- docs/source/conf.py | 2 ++ docs/source/whatsnew/index.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 11a3cb973d2..edb93094daa 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -87,8 +87,10 @@ def is_stable(extra): if is_stable(iprelease['_version_extra']): tags.add('ipystable') + print('Adding Tag: ipystable') else: tags.add('ipydev') + print('Adding Tag: ipydev') rst_prolog += """ .. warning:: diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 51aa0fe1aa4..a8e7df908d6 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -21,6 +21,7 @@ What's new in IPython Developpement version in-progress features: .. toctree:: + development .. @@ -31,6 +32,7 @@ What's new in IPython .. toctree:: :hidden: + development This section documents the changes that have been made in various versions of From 5b63a313e93e33a1de68d221461d8fef14b0873c Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 15:52:58 +0200 Subject: [PATCH 0271/3726] catch BaseException in coroutine runner so we catch KeyboardInterrupt and the like, not just Exceptions --- IPython/core/interactiveshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index c2b57b0642e..62bc209d1c3 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2841,7 +2841,7 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures try: return runner(coro) - except Exception as e: + except BaseException as e: info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) result = ExecutionResult(info) result.error_in_exec = e From cae2b120220d0c544d75f55d71e62e0b35b3cdfa Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 10 Sep 2018 15:31:49 +0200 Subject: [PATCH 0272/3726] typos --- IPython/core/inputtransformer2.py | 1 - docs/source/interactive/autoawait.rst | 2 +- docs/source/whatsnew/index.rst | 6 +++--- docs/source/whatsnew/version0.13.rst | 2 +- docs/source/whatsnew/version3.rst | 2 +- docs/source/whatsnew/version7.rst | 4 ++-- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index f6973991a1d..20237ceb1cc 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -541,7 +541,6 @@ def transform_cell(self, cell: str) -> str: cell += '\n' # Ensure the cell has a trailing newline lines = cell.splitlines(keepends=True) for transform in self.cleanup_transforms + self.line_transforms: - #print(transform, lines) lines = transform(lines) lines = self.do_token_transforms(lines) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 0164f93560f..f0c2dab2819 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -5,7 +5,7 @@ Asynchronous in REPL: Autoawait .. note:: - This feature is experimental and behavior can change betwen python and + This feature is experimental and behavior can change between python and IPython version without prior deprecation. Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index a8e7df908d6..62ae2c11941 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -13,12 +13,12 @@ What's new in IPython ===================== .. - this will appear in the docs if we are nto releasing a versin (ie is - `_version_extra` in release.py is empty stringA + this will appear in the docs if we are not releasing a versin (ie is + `_version_extra` in release.py is empty string .. only:: ipydev - Developpement version in-progress features: + Development version in-progress features: .. toctree:: diff --git a/docs/source/whatsnew/version0.13.rst b/docs/source/whatsnew/version0.13.rst index b1106ebf6f5..63140125a5a 100644 --- a/docs/source/whatsnew/version0.13.rst +++ b/docs/source/whatsnew/version0.13.rst @@ -155,7 +155,7 @@ Other improvements to the Notebook These are some other notable small improvements to the notebook, in addition to many bug fixes and minor changes to add polish and robustness throughout: -* The notebook pager (the area at the bottom) is now resizeable by dragging its +* The notebook pager (the area at the bottom) is now Resizable by dragging its divider handle, a feature that had been requested many times by just about anyone who had used the notebook system. :ghpull:`1705`. diff --git a/docs/source/whatsnew/version3.rst b/docs/source/whatsnew/version3.rst index 56c5d9f56f0..f230943b5ae 100644 --- a/docs/source/whatsnew/version3.rst +++ b/docs/source/whatsnew/version3.rst @@ -182,7 +182,7 @@ Other new features * ``NotebookApp.webapp_settings`` is deprecated and replaced with the more informatively named ``NotebookApp.tornado_settings``. -* Using :magic:`timeit` prints warnings if there is atleast a 4x difference in timings +* Using :magic:`timeit` prints warnings if there is at least a 4x difference in timings between the slowest and fastest runs, since this might meant that the multiple runs are not independent of one another. diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index f53e3415307..7f35c4a4976 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -148,8 +148,8 @@ documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor, minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other. -Autoreload Improvment ---------------------- +Autoreload Improvement +---------------------- The magic ``%autoreload 2`` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated. From 578c1995e92b63d9e36d60b388df539d4bdcdecb Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Mon, 10 Sep 2018 12:17:40 -0700 Subject: [PATCH 0273/3726] updated what's new --- docs/source/whatsnew/development.rst | 7 ++++++- docs/source/whatsnew/pr/bash_raise_default.rst | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 docs/source/whatsnew/pr/bash_raise_default.rst diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 532ba742778..a4c5d0883ca 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -23,10 +23,15 @@ Need to be updated: pr/* +The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magic no raise by +default if the return code of the given code is non-zero (this halting execution +of further cells in a notebook). The behavior can be disable by passing the +``--no-raise-error`` flag. + .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. Backwards incompatible changes ------------------------------ -.. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. +.. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. \ No newline at end of file diff --git a/docs/source/whatsnew/pr/bash_raise_default.rst b/docs/source/whatsnew/pr/bash_raise_default.rst deleted file mode 100644 index dc2a14e0993..00000000000 --- a/docs/source/whatsnew/pr/bash_raise_default.rst +++ /dev/null @@ -1,4 +0,0 @@ -The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magic no raise by -default if the return code of the given code is non-zero (this halting execution -of further cells in a notebook). The behavior can be disable by passing the -``--no-raise-error`` flag. From 831619b11f207582b171d07096e30c49317dfb3c Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Mon, 10 Sep 2018 13:44:21 -0700 Subject: [PATCH 0274/3726] fix typos, migrate latest to release notes --- docs/source/whatsnew/development.rst | 7 +------ docs/source/whatsnew/version7.rst | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index a4c5d0883ca..532ba742778 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -23,15 +23,10 @@ Need to be updated: pr/* -The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magic no raise by -default if the return code of the given code is non-zero (this halting execution -of further cells in a notebook). The behavior can be disable by passing the -``--no-raise-error`` flag. - .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. Backwards incompatible changes ------------------------------ -.. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. \ No newline at end of file +.. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 7f35c4a4976..286c6054d11 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -9,25 +9,25 @@ IPython 7.0.0 .. warning:: - IPython 7.0 is currently in Beta, Feedback on API/changes and - addition/updates to this cahngelog are welcomed. + IPython 7.0 is currently in Beta. We welcome feedback on API/changes and + addition/updates to this changelog. -Released .... ...., 2017 +Released .... ...., 2018 IPython 7 include major features improvement as you can read in the following -changelog. This is also the second major version of IPython to stop support only -Python 3 – starting at Python 3.4. Python 2 is still still community supported -on the bugfix only 5.x branch, but we remind you that Python 2 EOL is Jan 1st -2020. +changelog. This is also the second major version of IPython to support only +Python 3 – starting at Python 3.4. Python 2 is still community supported +on the bugfix only 5.x branch, but we remind you that Python 2 "end of life" +is on Jan 1st 2020. We were able to backport bug fixes to the 5.x branch thanks to our backport bot which backported more than `70 Pull-Requests `_, but there are still many PRs that required manually work, and this is an area of the project were you can easily contribute by looking for `PRs still needed backport `_ -IPython 6.x branch will likely not see any further release unless we critical +IPython 6.x branch will likely not see any further release unless critical bugs are found. -Make sure you have pip > 9.0 before upgrading. You should be able to update by simply runngin +Make sure you have pip > 9.0 before upgrading. You should be able to update by simply running .. code:: @@ -44,7 +44,7 @@ Or if you have conda installed: Prompt Toolkit 2.0 ------------------ -IPython 7.0+ now use ``prompt_toolkit 2.0``, if you still need to use earlier +IPython 7.0+ now uses ``prompt_toolkit 2.0``, if you still need to use earlier ``prompt_toolkit`` version you may need to pin IPython to ``<7.0``. Autowait: Asynchronous REPL @@ -145,7 +145,7 @@ This took more than a year in the making, and the code was rebased a number of time leading to commit authorship that may have been lost in the final Pull-Request. Huge thanks to many people for contribution, discussion, code, documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor, -minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other. +minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many others. Autoreload Improvement @@ -194,7 +194,10 @@ un-deprecated in :ghpull:`11257` Make ``%run -n -i ...`` work correctly. Earlier, if ``%run`` was passed both arguments, ``-n`` would be silently ignored. See :ghpull:`10308` - +The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magics now raise +by default if the return code of the given code is non-zero (thus halting +execution of further cells in a notebook). The behavior can be disable by +passing the ``--no-raise-error`` flag. Deprecations From 5eb66cc010ddf87f0012e734b6a3bf954afb7aeb Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Mon, 10 Sep 2018 13:53:21 -0700 Subject: [PATCH 0275/3726] add preliminary 7.0 release stats --- docs/source/whatsnew/github-stats-7.rst | 61 +++++++++++++++++++++++++ docs/source/whatsnew/index.rst | 1 + 2 files changed, 62 insertions(+) create mode 100644 docs/source/whatsnew/github-stats-7.rst diff --git a/docs/source/whatsnew/github-stats-7.rst b/docs/source/whatsnew/github-stats-7.rst new file mode 100644 index 00000000000..9d83a31dd3f --- /dev/null +++ b/docs/source/whatsnew/github-stats-7.rst @@ -0,0 +1,61 @@ +Issues closed in the 7.x development cycle +========================================== + +Issues closed in 7.0 +-------------------- + + +GitHub stats for 2018/04/02 - 2018/09/XX (tag: 7.0.0) + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 10 issues and merged 62 pull requests. +The full list can be seen `on GitHub `__ + +The following 45 authors contributed 423 commits. + +* alphaCTzo7G +* Alyssa Whitwell +* Anatol Ulrich +* apunisal +* Benjamin Ragan-Kelley +* Chaz Reid +* Christoph +* Dale Jung +* Dave Hirschfeld +* dhirschf +* Doug Latornell +* Fernando Perez +* Fred Mitchell +* Gabriel Potter +* gpotter2 +* Grant Nestor +* Hugo +* J Forde +* Jonathan Slenders +* Jörg Dietrich +* Kyle Kelley +* luz.paz +* M Pacer +* Matthew R. Scott +* Matthew Seal +* Matthias Bussonnier +* meeseeksdev[bot] +* Olesya Baranova +* oscar6echo +* Paul Ganssle +* Paul Ivanov +* Peter Parente +* prasanth +* Shailyn javier Ortiz jimenez +* Sourav Singh +* Srinivas Reddy Thatiparthy +* stonebig +* Subhendu Ranjan Mishra +* Takafumi Arakaki +* Thomas A Caswell +* Thomas Kluyver +* Todd +* Wei Yen +* Yutao Yuan +* Zi Chong Kao diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 62ae2c11941..b6885b5a498 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -44,6 +44,7 @@ development work they do here in a user friendly format. :maxdepth: 1 version7 + github-stats-7 version6 github-stats-6 version5 From 2a62b70caecaac834be8b02149f6dd985b73d76c Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Mon, 10 Sep 2018 14:20:32 -0700 Subject: [PATCH 0276/3726] release 7.0.0b1 --- IPython/core/release.py | 2 +- docs/source/coredev/index.rst | 2 +- docs/source/whatsnew/index.rst | 8 ++++---- tools/build_release | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 525639e77a4..90dd15dd782 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -23,7 +23,7 @@ _version_minor = 0 _version_patch = 0 _version_extra = '.dev' -# _version_extra = 'rc2' +_version_extra = 'b1' # _version_extra = '' # Uncomment this for full releases # Construct full version string from these. diff --git a/docs/source/coredev/index.rst b/docs/source/coredev/index.rst index 67e2c01437c..32d00879021 100644 --- a/docs/source/coredev/index.rst +++ b/docs/source/coredev/index.rst @@ -209,7 +209,7 @@ the build procedure runs OK, and tests other steps in the release process. The ``build_release`` script will in particular verify that the version number match PEP 440, in order to avoid surprise at the time of build upload. -We encourage creating a test build of the docs as well. +We encourage creating a test build of the docs as well. 6. Create and push the new tag ------------------------------ diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index b6885b5a498..d96b53879b5 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -13,8 +13,8 @@ What's new in IPython ===================== .. - this will appear in the docs if we are not releasing a versin (ie is - `_version_extra` in release.py is empty string + this will appear in the docs if we are not releasing a version (ie if + `_version_extra` in release.py is an empty string) .. only:: ipydev @@ -25,8 +25,8 @@ What's new in IPython development .. - this make a hidden toctree that avoid sphinx to complain about documents - included nowhere when building docs for stable + this makes a hidden toctree that keeps sphinx from complaining about + documents included nowhere when building docs for stable .. only:: ipystable diff --git a/tools/build_release b/tools/build_release index 998010a1316..e963daa3f86 100755 --- a/tools/build_release +++ b/tools/build_release @@ -17,7 +17,9 @@ def build_release(): with open('docs/source/whatsnew/index.rst') as f: if ' development' in f.read(): - raise ValueError("Please remove `development` from what's new toctree for release") + pass + # raise ValueError("Please remove `development` from what's new toctree for release") + # Cleanup for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'), From 73d63355447cf806a440b8cfe22e8367d3665abf Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Mon, 10 Sep 2018 14:22:58 -0700 Subject: [PATCH 0277/3726] back to development --- IPython/core/release.py | 2 +- docs/source/coredev/index.rst | 2 +- tools/build_release | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 90dd15dd782..e6552cff6d3 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -23,7 +23,7 @@ _version_minor = 0 _version_patch = 0 _version_extra = '.dev' -_version_extra = 'b1' +# _version_extra = 'b1' # _version_extra = '' # Uncomment this for full releases # Construct full version string from these. diff --git a/docs/source/coredev/index.rst b/docs/source/coredev/index.rst index 32d00879021..89d2866c85e 100644 --- a/docs/source/coredev/index.rst +++ b/docs/source/coredev/index.rst @@ -222,7 +222,7 @@ Commit the changes to release.py:: Create and push the tag:: git tag -am "release $VERSION" "$VERSION" - git push origin --tags + git push origin $VERSION Update release.py back to ``x.y-dev`` or ``x.y-maint``, and re-add the ``development`` entry in ``docs/source/whatsnew/index.rst`` and push:: diff --git a/tools/build_release b/tools/build_release index e963daa3f86..998010a1316 100755 --- a/tools/build_release +++ b/tools/build_release @@ -17,9 +17,7 @@ def build_release(): with open('docs/source/whatsnew/index.rst') as f: if ' development' in f.read(): - pass - # raise ValueError("Please remove `development` from what's new toctree for release") - + raise ValueError("Please remove `development` from what's new toctree for release") # Cleanup for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'), From 7af058aae8ab8fa25023130d329d1886e1b256e5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 11 Sep 2018 11:43:20 +0200 Subject: [PATCH 0278/3726] cleanup build process --- tools/build_release | 9 +-------- tools/toollib.py | 15 +++++---------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/tools/build_release b/tools/build_release index 998010a1316..26dc9ec874a 100755 --- a/tools/build_release +++ b/tools/build_release @@ -4,7 +4,7 @@ import os from shutil import rmtree -from toollib import sh, pjoin, get_ipdir, cd, execfile, sdists, buildwheels +from toollib import sh, pjoin, get_ipdir, cd, sdists, buildwheels def build_release(): @@ -12,13 +12,6 @@ def build_release(): ipdir = get_ipdir() cd(ipdir) - # Load release info - execfile(pjoin('IPython', 'core', 'release.py'), globals()) - - with open('docs/source/whatsnew/index.rst') as f: - if ' development' in f.read(): - raise ValueError("Please remove `development` from what's new toctree for release") - # Cleanup for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'), pjoin('docs', 'source', 'api', 'generated')]: diff --git a/tools/toollib.py b/tools/toollib.py index fd160c510bb..510b54e99d4 100644 --- a/tools/toollib.py +++ b/tools/toollib.py @@ -3,6 +3,7 @@ # Library imports import os +import sys # Useful shorthands pjoin = os.path.join @@ -20,7 +21,7 @@ sdists = './setup.py sdist --formats=gztar' # Binary dists def buildwheels(): - sh('python3 setupegg.py bdist_wheel') + sh('{python} setupegg.py bdist_wheel'.format(python=sys.executable)) # Utility functions def sh(cmd): @@ -31,9 +32,6 @@ def sh(cmd): if stat: raise SystemExit("Command %s failed with code: %s" % (cmd, stat)) -# Backwards compatibility -c = sh - def get_ipdir(): """Get IPython directory from command line, or assume it's the one above.""" @@ -47,9 +45,6 @@ def get_ipdir(): raise SystemExit('Invalid ipython directory: %s' % ipdir) return ipdir -try: - execfile = execfile -except NameError: - def execfile(fname, globs, locs=None): - locs = locs or globs - exec(compile(open(fname).read(), fname, "exec"), globs, locs) +def execfile(fname, globs, locs=None): + locs = locs or globs + exec(compile(open(fname).read(), fname, "exec"), globs, locs) From 3a70733ce0734b123daa080e8e031c00ee85a4b8 Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Tue, 11 Sep 2018 15:39:18 -0400 Subject: [PATCH 0279/3726] typos Found via `codespell -q 3` --- IPython/terminal/pt_inputhooks/qt.py | 4 ++-- IPython/testing/plugin/test_refs.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index 7395ac39ebe..2ceda6bdc70 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -19,8 +19,8 @@ def inputhook(context): if not _already_warned: _already_warned = True warnings.warn( - 'The DISPLAY or WAYLAND_DISPLAY enviroment variable is ' - 'not set or empty and Qt5 requires this enviroment ' + 'The DISPLAY or WAYLAND_DISPLAY environment variable is ' + 'not set or empty and Qt5 requires this environment ' 'variable. Deactivate Qt5 code.' ) return diff --git a/IPython/testing/plugin/test_refs.py b/IPython/testing/plugin/test_refs.py index bd33942aa11..bd7ad8fb3e3 100644 --- a/IPython/testing/plugin/test_refs.py +++ b/IPython/testing/plugin/test_refs.py @@ -21,7 +21,7 @@ def doctest_run(): """ def doctest_runvars(): - """Test that variables defined in scripts get loaded correclty via %run. + """Test that variables defined in scripts get loaded correctly via %run. In [13]: run simplevars.py x is: 1 From 14ba55499c341ac8af8ada03f96388b7b6a7b891 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 11 Sep 2018 22:15:36 +0200 Subject: [PATCH 0280/3726] Remove include from MANIFEST --- MANIFEST.in | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9b03f9c9778..3a19fbf8c5b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,6 @@ include LICENSE include setupbase.py include setupegg.py include MANIFEST.in -include tox.ini include .mailmap recursive-exclude tools * @@ -19,9 +18,6 @@ graft scripts # Load main dir but exclude things we don't want in the distro graft IPython -# Include some specific files and data resources we need -include IPython/.git_commit_info.ini - # Documentation graft docs exclude docs/\#* From 2cd9f6b2e3caa147b0d618ca6578816ec71535a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ufl?= Date: Wed, 12 Sep 2018 11:22:16 +0200 Subject: [PATCH 0281/3726] [travis] Run tests against Python 3.7 and a non-outdated nightly --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc632dd978e..5b0281f3fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ # http://travis-ci.org/#!/ipython/ipython language: python python: - - "nightly" - - "3.7-dev" - 3.6 - 3.5 sudo: false @@ -35,6 +33,9 @@ after_success: - codecov matrix: + include: + - { python: "3.7", dist: xenial, sudo: true } + - { python: "nightly", dist: xenial, sudo: true } allow_failures: - python: nightly From bebb86e9358dd01bfee47832dbfaea87bd039ef2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 29 May 2018 09:27:32 -0700 Subject: [PATCH 0282/3726] Revert 3.7 AST fix Also start testing on 3.7-dev and build docs on 3.7 --- .travis.yml | 3 ++- IPython/core/compilerop.py | 21 ++------------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b0281f3fe7..09e16146476 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ script: - cd /tmp && iptest --coverage xml && cd - # On the latest Python only, make sure that the docs build. - | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then + if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then pip install -r docs/requirements.txt python tools/fixup_whats_new_pr.py make -C docs/ html SPHINXOPTS="-W" @@ -35,6 +35,7 @@ after_success: matrix: include: - { python: "3.7", dist: xenial, sudo: true } + - { python: "3.7-dev", dist: xenial, sudo: true } - { python: "nightly", dist: xenial, sudo: true } allow_failures: - python: nightly diff --git a/IPython/core/compilerop.py b/IPython/core/compilerop.py index c5abd061d00..6a055f93361 100644 --- a/IPython/core/compilerop.py +++ b/IPython/core/compilerop.py @@ -92,30 +92,13 @@ def __init__(self): linecache.checkcache = check_linecache_ipython - def _fix_module_ds(self, module): - """ - Starting in python 3.7 the AST for mule have changed, and if - the first expressions encountered is a string it is attached to the - `docstring` attribute of the `Module` ast node. - - This breaks IPython, as if this string is the only expression, IPython - will not return it as the result of the current cell. - """ - from ast import Str, Expr, Module, fix_missing_locations - docstring = getattr(module, 'docstring', None) - if not docstring: - return module - new_body=[Expr(Str(docstring, lineno=1, col_offset=0), lineno=1, col_offset=0)] - new_body.extend(module.body) - return fix_missing_locations(Module(new_body)) - def ast_parse(self, source, filename='', symbol='exec'): """Parse code to an AST with the current compiler flags active. Arguments are exactly the same as ast.parse (in the standard library), and are passed to the built-in compile function.""" - return self._fix_module_ds(compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)) - + return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) + def reset_compiler_flags(self): """Reset compiler flags to default state.""" # This value is copied from codeop.Compile.__init__, so if that ever From 347b7d479a4de3c7389631c0ab9fe634d3b33b91 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Sep 2018 14:23:18 +0200 Subject: [PATCH 0283/3726] update docs about autoawait --- docs/source/interactive/autoawait.rst | 98 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index f0c2dab2819..ab41a916afb 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -13,8 +13,8 @@ ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. The example given here are for terminal IPython, running async code in a -notebook interface or any other frontend using the Jupyter protocol will need to -use a newer version of IPykernel. The details of how async code runs in +notebook interface or any other frontend using the Jupyter protocol need to +IPykernel version 5.0 or above. The details of how async code runs in IPykernel will differ between IPython, IPykernel and their versions. When a supported library is used, IPython will automatically allow Futures and @@ -220,3 +220,97 @@ feel free to contribute improvements to this codebase and give us feedback. We invite you to thoroughly test this feature and report any unexpected behavior as well as propose any improvement. + +Using Autoawait in a notebook (IPykernel) +========================================= + +Update ipykernel to version 5.0 or greater:: + + pip install ipykernel ipython --upgrade + # or + conda install ipykernel ipython --upgrade + +This should automatically enable ``autoawait`` integration. Unlike terminal +IPython all code run on ``asynio`` eventloop, so creating a loop by hand will +not work, including with magics like ``%run`` or other framework that create +the eventloop themselves. In case like this you can try to use projects like +`nest_asyncio `_ and see discussion like `this one +`_ + +Difference between terminal IPython and IPykernel +================================================= + +The exact asynchronous code running behavior can varies between Terminal +IPython and IPykernel. The root cause of this behavior is due to IPykernel +having a _persistent_ ``asyncio`` loop running, while Terminal IPython start +and stop a loop for each code block. This can lead to surprising behavior in +some case if you are used to manipulate asyncio loop yourself, see for example +:ghissue:`11303` for a longer discussion but here are some of the astonishing +cases. + +This behavior is an implementation detail, and should not be relied upon. It +can change without warnings in future versions of IPython. + +In terminal IPython a loop is started for each code blocks only if there is top +level async code:: + + $ ipython + In [1]: import asyncio + ...: asyncio.get_event_loop() + Out[1]: <_UnixSelectorEventLoop running=False closed=False debug=False> + + In [2]: + + In [2]: import asyncio + ...: await asyncio.sleep(0) + ...: asyncio.get_event_loop() + Out[2]: <_UnixSelectorEventLoop running=True closed=False debug=False> + +See that ``running`` is ``True`` only in the case were we ``await sleep()`` + +In a Notebook, with ipykernel the asyncio eventloop is always running:: + + $ jupyter notebook + In [1]: import asyncio + ...: loop1 = asyncio.get_event_loop() + ...: loop1 + Out[1]: <_UnixSelectorEventLoop running=True closed=False debug=False> + + In [2]: loop2 = asyncio.get_event_loop() + ...: loop2 + Out[2]: <_UnixSelectorEventLoop running=True closed=False debug=False> + + In [3]: loop1 is loop2 + Out[3]: True + +In Terminal IPython background task are only processed while the foreground +task is running, and IIF the foreground task is async:: + + $ ipython + In [1]: import asyncio + ...: + ...: async def repeat(msg, n): + ...: for i in range(n): + ...: print(f"{msg} {i}") + ...: await asyncio.sleep(1) + ...: return f"{msg} done" + ...: + ...: asyncio.ensure_future(repeat("background", 10)) + Out[1]: :3>> + + In [2]: await asyncio.sleep(3) + background 0 + background 1 + background 2 + background 3 + + In [3]: import time + ...: time.sleep(5) + + In [4]: await asyncio.sleep(3) + background 4 + background 5 + background 6 + +In a Notebook, QtConsole, or any other frontend using IPykernel, background +task should behave as expected. From 28a8ea639a7a37bfb03ee87d397ea5e69dcc537e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Sep 2018 18:34:07 +0200 Subject: [PATCH 0284/3726] Add some inputtransformer test cases --- IPython/core/tests/test_inputtransformer2.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index a3e4889030d..f78a0b37158 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -5,6 +5,7 @@ transformations. """ import nose.tools as nt +import string from IPython.core import inputtransformer2 as ipt2 from IPython.core.inputtransformer2 import make_tokens_by_line @@ -100,6 +101,16 @@ [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] ) +def check_make_token_by_line_never_ends_empty(): + """ + Check that not sequence of single or double characters ends up leading to en empty list of tokens + """ + from string import printable + for c in printable: + nt.assert_not_equal(make_tokens_by_line(c)[-1], []) + for k in printable: + nt.assert_not_equal(make_tokens_by_line(c+k)[-1], []) + def check_find(transformer, case, match=True): sample, expected_start, _ = case tbl = make_tokens_by_line(sample) @@ -190,6 +201,17 @@ def test_check_complete(): nt.assert_equal(cc("for a in range(5):"), ('incomplete', 4)) nt.assert_equal(cc("raise = 2"), ('invalid', None)) nt.assert_equal(cc("a = [1,\n2,"), ('incomplete', 0)) + nt.assert_equal(cc(")"), ('incomplete', 0)) + nt.assert_equal(cc("\\\r\n"), ('incomplete', 0)) nt.assert_equal(cc("a = '''\n hi"), ('incomplete', 3)) nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None)) nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash + + # no need to loop on all the letters/numbers. + short = '12abAB'+string.printable[62:] + for c in short: + # test does not raise: + cc(c) + for k in short: + cc(c+k) + From d3bd9b24fd7e4c9361ef575fd24e2a915976130f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Sep 2018 18:34:17 +0200 Subject: [PATCH 0285/3726] Some propose fixes. I'm not totally happy as things like `0?` now raise a SyntaxError without proper filename and context. Also there is now the fact that transformers can return None to stop all transformations, which I don't like. Closes #11306 --- IPython/core/inputtransformer2.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 20237ceb1cc..886aa02eec4 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -418,6 +418,8 @@ def transform(self, lines): lines_after = lines[self.q_line + 1:] m = _help_end_re.search(content) + if not m: + raise SyntaxError(content) assert m is not None, content target = m.group(1) esc = m.group(3) @@ -460,6 +462,8 @@ def make_tokens_by_line(lines): except tokenize.TokenError: # Input ended in a multiline string or expression. That's OK for us. pass + if not tokens_by_line[-1]: + tokens_by_line.pop() return tokens_by_line @@ -524,6 +528,9 @@ def do_one_token_transform(self, lines): return False, lines transformer = min(candidates, key=TokenTransformBase.sortby) + transformed = transformer.transform(lines) + if transformed is None: + return False, lines return True, transformer.transform(lines) def do_token_transforms(self, lines): @@ -591,10 +598,13 @@ def check_complete(self, cell: str): return 'invalid', None tokens_by_line = make_tokens_by_line(lines) + if not tokens_by_line: + return 'incomplete', find_last_indent(lines) if tokens_by_line[-1][-1].type != tokenize.ENDMARKER: # We're in a multiline string or expression return 'incomplete', find_last_indent(lines) - + if len(tokens_by_line) == 1: + return 'incomplete', find_last_indent(lines) # Find the last token on the previous line that's not NEWLINE or COMMENT toks_last_line = tokens_by_line[-2] ix = len(toks_last_line) - 1 From 8657acdd981ce22cad0fd6fcd9c9132a1160f701 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Sep 2018 18:42:05 +0200 Subject: [PATCH 0286/3726] Better alternative; try each transformer in a row, They can actually raise a SyntaxError in which case the transformation will be aborted. --- IPython/core/inputtransformer2.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 886aa02eec4..1abd2cf95cb 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -526,12 +526,13 @@ def do_one_token_transform(self, lines): if not candidates: # Nothing to transform return False, lines - - transformer = min(candidates, key=TokenTransformBase.sortby) - transformed = transformer.transform(lines) - if transformed is None: - return False, lines - return True, transformer.transform(lines) + ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby) + for transformer in ordered_transformers: + try: + return True, transformer.transform(lines) + except SyntaxError: + pass + return False, lines def do_token_transforms(self, lines): for _ in range(TRANSFORM_LOOP_LIMIT): From 3891edd0298e433315cad8d79018831921a93ec5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 13 Sep 2018 10:25:18 +0200 Subject: [PATCH 0287/3726] fix Paul's comments --- docs/source/interactive/autoawait.rst | 41 ++++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index ab41a916afb..f0da45a3e4f 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -13,7 +13,7 @@ ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. The example given here are for terminal IPython, running async code in a -notebook interface or any other frontend using the Jupyter protocol need to +notebook interface or any other frontend using the Jupyter protocol needs IPykernel version 5.0 or above. The details of how async code runs in IPykernel will differ between IPython, IPykernel and their versions. @@ -57,8 +57,8 @@ Should behave as expected in the IPython REPL:: You can use the ``c.InteractiveShell.autoawait`` configuration option and set it -to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also -use the :magic:`%autoawait` magic to toggle the behavior at runtime:: +to :any:`False` to deactivate automatic wrapping of asynchronous code. You can +also use the :magic:`%autoawait` magic to toggle the behavior at runtime:: In [1]: %autoawait False @@ -110,9 +110,9 @@ In the above example, ``async with`` at top level scope is a syntax error in Python. Using this mode can have unexpected consequences if used in interaction with -other features of IPython and various registered extensions. In particular if you -are a direct or indirect user of the AST transformers, these may not apply to -your code. +other features of IPython and various registered extensions. In particular if +you are a direct or indirect user of the AST transformers, these may not apply +to your code. When using command line IPython, the default loop (or runner) does not process in the background, so top level asynchronous code must finish for the REPL to @@ -231,25 +231,26 @@ Update ipykernel to version 5.0 or greater:: conda install ipykernel ipython --upgrade This should automatically enable ``autoawait`` integration. Unlike terminal -IPython all code run on ``asynio`` eventloop, so creating a loop by hand will -not work, including with magics like ``%run`` or other framework that create -the eventloop themselves. In case like this you can try to use projects like -`nest_asyncio `_ and see discussion like `this one +IPython, all code runs on ``asynio`` eventloop, so creating a loop by hand will +not work, including with magics like ``%run`` or other frameworks that create +the eventloop themselves. In case like theses you can try to use projects like +`nest_asyncio `_ and see discussion +like `this one `_ Difference between terminal IPython and IPykernel ================================================= -The exact asynchronous code running behavior can varies between Terminal -IPython and IPykernel. The root cause of this behavior is due to IPykernel -having a _persistent_ ``asyncio`` loop running, while Terminal IPython start -and stop a loop for each code block. This can lead to surprising behavior in -some case if you are used to manipulate asyncio loop yourself, see for example +The exact asynchronous code running behavior varies between Terminal IPython and +IPykernel. The root cause of this behavior is due to IPykernel having a +_persistent_ ``asyncio`` loop running, while Terminal IPython starts and stop a +loop for each code block. This can lead to surprising behavior in some case if +you are used to manipulate asyncio loop yourself, see for example :ghissue:`11303` for a longer discussion but here are some of the astonishing cases. -This behavior is an implementation detail, and should not be relied upon. It -can change without warnings in future versions of IPython. +This behavior is an implementation detail, and should not be relied upon. It can +change without warnings in future versions of IPython. In terminal IPython a loop is started for each code blocks only if there is top level async code:: @@ -283,8 +284,8 @@ In a Notebook, with ipykernel the asyncio eventloop is always running:: In [3]: loop1 is loop2 Out[3]: True -In Terminal IPython background task are only processed while the foreground -task is running, and IIF the foreground task is async:: +In Terminal IPython background tasks are only processed while the foreground +task is running, if and only if the foreground task is async:: $ ipython In [1]: import asyncio @@ -313,4 +314,4 @@ task is running, and IIF the foreground task is async:: background 6 In a Notebook, QtConsole, or any other frontend using IPykernel, background -task should behave as expected. +tasks should behave as expected. From be67876e66baa759fe5da336ef0634aee761a4a2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 13 Sep 2018 10:26:25 +0200 Subject: [PATCH 0288/3726] one more rephrasing/plural --- docs/source/interactive/autoawait.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index f0da45a3e4f..ad5a16d4fa4 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -12,10 +12,10 @@ Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the ability to run asynchronous code from the REPL. Constructs which are :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. -The example given here are for terminal IPython, running async code in a +The examples given here are for terminal IPython, running async code in a notebook interface or any other frontend using the Jupyter protocol needs -IPykernel version 5.0 or above. The details of how async code runs in -IPykernel will differ between IPython, IPykernel and their versions. +IPykernel version 5.0 or above. The details of how async code runs in IPykernel +will differ between IPython, IPykernel and their versions. When a supported library is used, IPython will automatically allow Futures and Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await From 1aa27baa979ac16ee8d5f3bfcb189410ee20d181 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 13 Sep 2018 10:26:54 +0200 Subject: [PATCH 0289/3726] one more plural --- docs/source/interactive/autoawait.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index ad5a16d4fa4..ce66afbdc36 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -243,7 +243,7 @@ Difference between terminal IPython and IPykernel The exact asynchronous code running behavior varies between Terminal IPython and IPykernel. The root cause of this behavior is due to IPykernel having a -_persistent_ ``asyncio`` loop running, while Terminal IPython starts and stop a +_persistent_ ``asyncio`` loop running, while Terminal IPython starts and stops a loop for each code block. This can lead to surprising behavior in some case if you are used to manipulate asyncio loop yourself, see for example :ghissue:`11303` for a longer discussion but here are some of the astonishing From 18017f974d5f684973ee4c79553281f65df2b83e Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Thu, 13 Sep 2018 22:45:54 -0700 Subject: [PATCH 0290/3726] minor typo and style tweak --- docs/source/interactive/autoawait.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index ce66afbdc36..2603e96ba3d 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -231,11 +231,10 @@ Update ipykernel to version 5.0 or greater:: conda install ipykernel ipython --upgrade This should automatically enable ``autoawait`` integration. Unlike terminal -IPython, all code runs on ``asynio`` eventloop, so creating a loop by hand will +IPython, all code runs on ``asyncio`` eventloop, so creating a loop by hand will not work, including with magics like ``%run`` or other frameworks that create -the eventloop themselves. In case like theses you can try to use projects like -`nest_asyncio `_ and see discussion -like `this one +the eventloop themselves. In cases like these you can try to use projects like +`nest_asyncio `_ and follow `this discussion `_ Difference between terminal IPython and IPykernel @@ -311,7 +310,7 @@ task is running, if and only if the foreground task is async:: In [4]: await asyncio.sleep(3) background 4 background 5 - background 6 + background 6g In a Notebook, QtConsole, or any other frontend using IPykernel, background tasks should behave as expected. From f3bf1309c5c3795c411fff2de53428d32dc93649 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 14 Sep 2018 11:15:42 +0200 Subject: [PATCH 0291/3726] Rst formatting (typo and decrease level to not be in sidebar) --- docs/source/interactive/autoawait.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index 2603e96ba3d..cb1d4f96fb7 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -122,7 +122,7 @@ no network request is done between ``In[1]`` and ``In[2]``. Effects on IPython.embed() -========================== +-------------------------- IPython core being asynchronous, the use of ``IPython.embed()`` will now require a loop to run. By default IPython will use a fake coroutine runner which should @@ -133,7 +133,7 @@ You can set explicitly a coroutine runner for ``embed()`` if you desire to run asynchronous code, the exact behavior is though undefined. Effects on Magics -================= +----------------- A couple of magics (``%%timeit``, ``%timeit``, ``%%time``, ``%%prun``) have not yet been updated to work with asynchronous code and will raise syntax errors @@ -142,7 +142,7 @@ those, and extra cases we haven't caught yet. We hope for better support in Cor Python for top-level Async code. Internals -========= +--------- As running asynchronous code is not supported in interactive REPL (as of Python 3.7) we have to rely to a number of complex workaround and heuristic to allow @@ -222,7 +222,7 @@ We invite you to thoroughly test this feature and report any unexpected behavior as well as propose any improvement. Using Autoawait in a notebook (IPykernel) -========================================= +----------------------------------------- Update ipykernel to version 5.0 or greater:: @@ -238,11 +238,11 @@ the eventloop themselves. In cases like these you can try to use projects like `_ Difference between terminal IPython and IPykernel -================================================= +------------------------------------------------- The exact asynchronous code running behavior varies between Terminal IPython and IPykernel. The root cause of this behavior is due to IPykernel having a -_persistent_ ``asyncio`` loop running, while Terminal IPython starts and stops a +*persistent* ``asyncio`` loop running, while Terminal IPython starts and stops a loop for each code block. This can lead to surprising behavior in some case if you are used to manipulate asyncio loop yourself, see for example :ghissue:`11303` for a longer discussion but here are some of the astonishing From ce22f49932fd4f071804e59f3c9d3f7042b917dd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 14 Sep 2018 15:00:12 +0200 Subject: [PATCH 0292/3726] Fix magic directive and role. We were directly modifying state instead of using blessed API (also some api will change so be future proof with sphinx master). This should restore some ability of the :magic: and :cellmagic: directive to both support link to magics name when the text in the directive contains (or not) leading % and %%, it will also automatically prepend the right number of % for magics and cell magics. A couple of other related fixes. --- docs/source/config/extensions/autoreload.rst | 2 ++ docs/source/interactive/autoawait.rst | 17 ++++++----- docs/source/whatsnew/version7.rst | 31 ++++++++++++-------- docs/sphinxext/github.py | 11 ++++--- docs/sphinxext/magics.py | 5 ++-- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/docs/source/config/extensions/autoreload.rst b/docs/source/config/extensions/autoreload.rst index 619605e8346..3b354898298 100644 --- a/docs/source/config/extensions/autoreload.rst +++ b/docs/source/config/extensions/autoreload.rst @@ -4,4 +4,6 @@ autoreload ========== +.. magic:: autoreload + .. automodule:: IPython.extensions.autoreload diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index cb1d4f96fb7..f52aae56e6f 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -58,7 +58,7 @@ Should behave as expected in the IPython REPL:: You can use the ``c.InteractiveShell.autoawait`` configuration option and set it to :any:`False` to deactivate automatic wrapping of asynchronous code. You can -also use the :magic:`%autoawait` magic to toggle the behavior at runtime:: +also use the :magic:`autoawait` magic to toggle the behavior at runtime:: In [1]: %autoawait False @@ -127,7 +127,7 @@ Effects on IPython.embed() IPython core being asynchronous, the use of ``IPython.embed()`` will now require a loop to run. By default IPython will use a fake coroutine runner which should allow ``IPython.embed()`` to be nested. Though this will prevent usage of the -``autoawait`` feature when using IPython embed. +:magic:`autoawait` feature when using IPython embed. You can set explicitly a coroutine runner for ``embed()`` if you desire to run asynchronous code, the exact behavior is though undefined. @@ -230,11 +230,12 @@ Update ipykernel to version 5.0 or greater:: # or conda install ipykernel ipython --upgrade -This should automatically enable ``autoawait`` integration. Unlike terminal -IPython, all code runs on ``asyncio`` eventloop, so creating a loop by hand will -not work, including with magics like ``%run`` or other frameworks that create -the eventloop themselves. In cases like these you can try to use projects like -`nest_asyncio `_ and follow `this discussion +This should automatically enable :magic:`autoawait` integration. Unlike +terminal IPython, all code runs on ``asyncio`` eventloop, so creating a loop by +hand will not work, including with magics like :magic:`%run` or other +frameworks that create the eventloop themselves. In cases like these you can +try to use projects like `nest_asyncio +`_ and follow `this discussion `_ Difference between terminal IPython and IPykernel @@ -242,7 +243,7 @@ Difference between terminal IPython and IPykernel The exact asynchronous code running behavior varies between Terminal IPython and IPykernel. The root cause of this behavior is due to IPykernel having a -*persistent* ``asyncio`` loop running, while Terminal IPython starts and stops a +*persistent* `asyncio` loop running, while Terminal IPython starts and stops a loop for each code block. This can lead to surprising behavior in some case if you are used to manipulate asyncio loop yourself, see for example :ghissue:`11303` for a longer discussion but here are some of the astonishing diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 286c6054d11..f5a32cdcc9e 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -114,10 +114,10 @@ Non-Asynchronous code ~~~~~~~~~~~~~~~~~~~~~ As the internal API of IPython are now asynchronous, IPython need to run under -an even loop. In order to allow many workflow, (like using the ``%run`` magic, -or copy_pasting code that explicitly starts/stop event loop), when top-level code -is detected as not being asynchronous, IPython code is advanced via a -pseudo-synchronous runner, and will not may not advance pending tasks. +an even loop. In order to allow many workflow, (like using the :magic:`%run` +magic, or copy_pasting code that explicitly starts/stop event loop), when +top-level code is detected as not being asynchronous, IPython code is advanced +via a pseudo-synchronous runner, and will not may not advance pending tasks. Change to Nested Embed ~~~~~~~~~~~~~~~~~~~~~~ @@ -151,11 +151,17 @@ minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many others. Autoreload Improvement ---------------------- -The magic ``%autoreload 2`` now captures new methods added to classes. Earlier, only methods existing as of the initial import were being tracked and updated. +The magic :magic:`%autoreload 2 ` now captures new methods added to +classes. Earlier, only methods existing as of the initial import were being +tracked and updated. -This new feature helps dual environment development - Jupyter+IDE - where the code gradually moves from notebook cells to package files, as it gets structured. +This new feature helps dual environment development - Jupyter+IDE - where the +code gradually moves from notebook cells to package files, as it gets +structured. -**Example**: An instance of the class `MyClass` will be able to access the method `cube()` after it is uncommented and the file `file1.py` saved on disk. +**Example**: An instance of the class ``MyClass`` will be able to access the +method ``cube()`` after it is uncommented and the file ``file1.py`` saved on +disk. ..code:: @@ -191,13 +197,14 @@ Misc The autoindent feature that was deprecated in 5.x was re-enabled and un-deprecated in :ghpull:`11257` -Make ``%run -n -i ...`` work correctly. Earlier, if ``%run`` was passed both arguments, ``-n`` would be silently ignored. See :ghpull:`10308` +Make :magic:`%run -n -i ... ` work correctly. Earlier, if :magic:`%run` was +passed both arguments, ``-n`` would be silently ignored. See :ghpull:`10308` -The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magics now raise -by default if the return code of the given code is non-zero (thus halting -execution of further cells in a notebook). The behavior can be disable by -passing the ``--no-raise-error`` flag. +The :cellmagic:`%%script`` (as well as :cellmagic:`%%bash``, +:cellmagic:`%%ruby``... ) cell magics now raise by default if the return code of +the given code is non-zero (thus halting execution of further cells in a +notebook). The behavior can be disable by passing the ``--no-raise-error`` flag. Deprecations diff --git a/docs/sphinxext/github.py b/docs/sphinxext/github.py index 8f0ffc0d978..dcc025006db 100644 --- a/docs/sphinxext/github.py +++ b/docs/sphinxext/github.py @@ -19,6 +19,9 @@ from docutils import nodes, utils from docutils.parsers.rst.roles import set_classes +from sphinx.util.logging import getLogger + +info = getLogger(__name__).info def make_link_node(rawtext, app, type, slug, options): """Create a link to a github resource. @@ -75,7 +78,7 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] app = inliner.document.settings.env.app - #app.info('issue %r' % text) + #info('issue %r' % text) if 'pull' in name.lower(): category = 'pull' elif 'issue' in name.lower(): @@ -105,7 +108,7 @@ def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param content: The directive content for customization. """ app = inliner.document.settings.env.app - #app.info('user link %r' % text) + #info('user link %r' % text) ref = 'https://www.github.com/' + text node = nodes.reference(rawtext, text, refuri=ref, **options) return [node], [] @@ -126,7 +129,7 @@ def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param content: The directive content for customization. """ app = inliner.document.settings.env.app - #app.info('user link %r' % text) + #info('user link %r' % text) try: base = app.config.github_project_url if not base: @@ -146,7 +149,7 @@ def setup(app): :param app: Sphinx application context. """ - app.info('Initializing GitHub plugin') + info('Initializing GitHub plugin') app.add_role('ghissue', ghissue_role) app.add_role('ghpull', ghissue_role) app.add_role('ghuser', ghuser_role) diff --git a/docs/sphinxext/magics.py b/docs/sphinxext/magics.py index 913a0c51beb..d96b41c6e17 100644 --- a/docs/sphinxext/magics.py +++ b/docs/sphinxext/magics.py @@ -37,9 +37,10 @@ class CellMagicRole(LineMagicRole): def setup(app): app.add_object_type('magic', 'magic', 'pair: %s; magic command', parse_magic) - StandardDomain.roles['magic'] = LineMagicRole() + app.add_role_to_domain('std', 'magic', LineMagicRole(), override=True) + app.add_object_type('cellmagic', 'cellmagic', 'pair: %s; cell magic', parse_cell_magic) - StandardDomain.roles['cellmagic'] = CellMagicRole() + app.add_role_to_domain('std', 'cellmagic', CellMagicRole(), override=True) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} return metadata From 91dc094f7c08742198256e25c8fd34e773150dc8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 14 Sep 2018 15:22:16 +0200 Subject: [PATCH 0293/3726] Update autoawait.rst --- docs/source/interactive/autoawait.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/interactive/autoawait.rst b/docs/source/interactive/autoawait.rst index f52aae56e6f..61611dc8e86 100644 --- a/docs/source/interactive/autoawait.rst +++ b/docs/source/interactive/autoawait.rst @@ -58,7 +58,7 @@ Should behave as expected in the IPython REPL:: You can use the ``c.InteractiveShell.autoawait`` configuration option and set it to :any:`False` to deactivate automatic wrapping of asynchronous code. You can -also use the :magic:`autoawait` magic to toggle the behavior at runtime:: +also use the :magic:`%autoawait` magic to toggle the behavior at runtime:: In [1]: %autoawait False @@ -127,7 +127,7 @@ Effects on IPython.embed() IPython core being asynchronous, the use of ``IPython.embed()`` will now require a loop to run. By default IPython will use a fake coroutine runner which should allow ``IPython.embed()`` to be nested. Though this will prevent usage of the -:magic:`autoawait` feature when using IPython embed. +:magic:`%autoawait` feature when using IPython embed. You can set explicitly a coroutine runner for ``embed()`` if you desire to run asynchronous code, the exact behavior is though undefined. From 619dbb35c67f8dc084e4fb331768c5e715e5ee5c Mon Sep 17 00:00:00 2001 From: Yarko Tymciurak Date: Fri, 14 Sep 2018 08:59:28 -0500 Subject: [PATCH 0294/3726] update documentation to add beta install instructions --- docs/source/whatsnew/version7.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 286c6054d11..a26054bd7c0 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -33,6 +33,15 @@ Make sure you have pip > 9.0 before upgrading. You should be able to update by s pip install ipython --upgrade +.. only:: ipydev + + To update the beta version, run + + .. code:: + + pip install ipython --upgrade --pre + + Or if you have conda installed: .. code:: From d1c745b99f32e9674fdc0ebd62b999610f74ed40 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 15 Sep 2018 10:02:13 +0200 Subject: [PATCH 0295/3726] update instructions: --pre apply to rc as well --- docs/source/whatsnew/version7.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index bcd33fbad85..05b8c9b7d42 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -35,7 +35,8 @@ Make sure you have pip > 9.0 before upgrading. You should be able to update by s .. only:: ipydev - To update the beta version, run + If you are trying to install or update an ``alpha``, ``beta``, or ``rc`` + version, use pip ``--pre`` flag. .. code:: From 0ec527d546cad252ca42e04619a77220ed829670 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 15 Sep 2018 11:49:59 +0200 Subject: [PATCH 0296/3726] Remove implicit dependency to ipython_genutils. This was installed because we rely on traitlets. `indent` behavior is _slightly_ different in the sens that white lines may not have the same number of whitespace. --- docs/autogen_config.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/autogen_config.py b/docs/autogen_config.py index f7913488a49..e8af5f239bf 100755 --- a/docs/autogen_config.py +++ b/docs/autogen_config.py @@ -11,7 +11,36 @@ options = join(here, 'source', 'config', 'options') generated = join(options, 'config-generated.txt') -from ipython_genutils.text import indent, dedent +import textwrap +indent = lambda text,n: textwrap.indent(text,n*' ') + +def dedent(text): + """Equivalent of textwrap.dedent that ignores unindented first line. + + This means it will still dedent strings like: + '''foo + is a bar + ''' + + For use in wrap_paragraphs. + """ + + if text.startswith('\n'): + # text starts with blank line, don't ignore the first line + return textwrap.dedent(text) + + # split first line + splits = text.split('\n',1) + if len(splits) == 1: + # only one line + return textwrap.dedent(text) + + first, rest = splits + # dedent everything but the first line + rest = textwrap.dedent(rest) + return '\n'.join([first, rest]) + + def interesting_default_value(dv): if (dv is None) or (dv is Undefined): From 840ab76cf17579edd44ea742ba108a997fe9cc19 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 15 Sep 2018 06:59:20 -0500 Subject: [PATCH 0297/3726] Allow showing dict keys only --- IPython/core/completer.py | 68 +++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 04d7a9f1f67..9858236905d 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -350,7 +350,7 @@ class Completion: Completion object used and return by IPython completers. .. warning:: Unstable - + This function is unstable, API may change without warning. It will also raise unless use in proper context manager. @@ -591,7 +591,7 @@ class Completer(Configurable): 'information for experimental jedi integration.')\ .tag(config=True) - backslash_combining_completions = Bool(True, + backslash_combining_completions = Bool(True, help="Enable unicode completions, e.g. \\alpha . " "Includes completion of latex commands, unicode names, and expanding " "unicode characters back to latex commands.").tag(config=True) @@ -693,7 +693,7 @@ def attr_matches(self, text): # Another option, seems to work great. Catches things like ''. m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) - + if m: expr, attr = m.group(1, 3) elif self.greedy: @@ -703,7 +703,7 @@ def attr_matches(self, text): expr, attr = m2.group(1,2) else: return [] - + try: obj = eval(expr, self.namespace) except: @@ -738,7 +738,7 @@ def get__all__entries(obj): words = getattr(obj, '__all__') except: return [] - + return [w for w in words if isinstance(w, str)] @@ -887,14 +887,14 @@ def _safe_isinstance(obj, module, class_name): def back_unicode_name_matches(text): u"""Match unicode characters back to unicode name - + This does ``☃`` -> ``\\snowman`` Note that snowman is not a valid python3 combining character but will be expanded. Though it will not recombine back to the snowman character by the completion machinery. This will not either back-complete standard sequences like \\n, \\b ... - + Used on Python 3 only. """ if len(text)<2: @@ -917,7 +917,7 @@ def back_unicode_name_matches(text): def back_latex_name_matches(text:str): """Match latex characters back to unicode name - + This does ``\\ℵ`` -> ``\\aleph`` Used on Python 3 only. @@ -991,7 +991,7 @@ def _make_signature(completion)-> str: class IPCompleter(Completer): """Extension of the completer class with IPython-specific features""" - + @observe('greedy') def _greedy_changed(self, change): """update the splitter and readline delims when greedy is changed""" @@ -999,36 +999,39 @@ def _greedy_changed(self, change): self.splitter.delims = GREEDY_DELIMS else: self.splitter.delims = DELIMS - + + dict_keys_only = Bool(False, + help="""Whether to show dict key matches only""") + merge_completions = Bool(True, help="""Whether to merge completion results into a single list - + If False, only the completion results from the first non-empty completer will be returned. """ ).tag(config=True) omit__names = Enum((0,1,2), default_value=2, help="""Instruct the completer to omit private method names - + Specifically, when completing on ``object.``. - + 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. """ ).tag(config=True) limit_to__all__ = Bool(False, help=""" DEPRECATED as of version 5.0. - + Instruct the completer to use __all__ for the completion - + Specifically, when completing on ``object.``. - + When True: only those names in obj.__all__ will be included. - + When False [default]: the __all__ attribute is ignored """, ).tag(config=True) @@ -1061,7 +1064,7 @@ def __init__(self, shell=None, namespace=None, global_namespace=None, secondary optional dict for completions, to handle cases (such as IPython embedded inside functions) where both Python scopes are visible. - + use_readline : bool, optional DEPRECATED, ignored since IPython 6.0, will have no effects """ @@ -1113,6 +1116,9 @@ def __init__(self, shell=None, namespace=None, global_namespace=None, @property def matchers(self): """All active matcher routines for completion""" + if self.dict_keys_only: + return [self.dict_key_matches] + if self.use_jedi: return [ self.file_matches, @@ -1621,7 +1627,7 @@ def get_keys(obj): closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims) if not matches: return matches - + # get the cursor position of # - the text being completed # - the start of the key text @@ -1632,13 +1638,13 @@ def get_keys(obj): completion_start = key_start + token_offset else: key_start = completion_start = match.end() - + # grab the leading prefix, to make sure all completions start with `text` if text_start > key_start: leading = '' else: leading = text[text_start:completion_start] - + # the index of the `[` character bracket_idx = match.end(1) @@ -1657,18 +1663,18 @@ def get_keys(obj): # brackets were opened inside text, maybe close them if not continuation.startswith(']'): suf += ']' - + return [leading + k + suf for k in matches] def unicode_name_matches(self, text): u"""Match Latex-like syntax for unicode characters base on the name of the character. - + This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` Works only on valid python 3 identifier, or on combining characters that will combine to form a valid identifier. - + Used on Python 3 only. """ slashpos = text.rfind('\\') @@ -1686,7 +1692,7 @@ def unicode_name_matches(self, text): def latex_matches(self, text): u"""Match Latex syntax for unicode characters. - + This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` Used on Python 3 only. @@ -1758,13 +1764,13 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: Returns an iterator over the possible completions .. warning:: Unstable - + This function is unstable, API may change without warning. It will also raise unless use in proper context manager. Parameters ---------- - + text:str Full text of the current input, multi line string. offset:int @@ -1793,7 +1799,7 @@ def completions(self, text: str, offset: int)->Iterator[Completion]: and usual IPython completion. .. note:: - + Completions are not completely deduplicated yet. If identical completions are coming from different sources this function does not ensure that each completion object will only be present once. @@ -1988,7 +1994,7 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, if name_text: return name_text, name_matches[:MATCHES_LIMIT], \ [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), () - + # If no line buffer is given, assume the input text is all there was if line_buffer is None: From 67eed53264b93d265ed1d4b136c7ba18076bc1d1 Mon Sep 17 00:00:00 2001 From: Audrey Dutcher Date: Wed, 15 Aug 2018 17:14:23 -0700 Subject: [PATCH 0298/3726] Fix `up` through generators in postmortem debugger By passing the lowest traceback element into the debugger, we require it to use frame.f_back to find older frames. However, f_back is always None for generator frames. By providing a higher-up traceback element, pdb can traverse down the traceback.tb_next links, which do work correctly across generator calls. Furthermore, pdb will not do the right thing if it is provided with a frame/traceback pair that do not correspond to each other - it will duplicate parts of the callstack. Instead, we provide None as a frame, which will cause the debugger to start at the deepest frame, which is the desired behavior here. --- IPython/core/ultratb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 3b97a82dd8d..78a4b3bf2c3 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -1203,7 +1203,7 @@ def debugger(self, force=False): if etb and etb.tb_next: etb = etb.tb_next self.pdb.botframe = etb.tb_frame - self.pdb.interaction(self.tb.tb_frame, self.tb) + self.pdb.interaction(None, etb) if hasattr(self, 'tb'): del self.tb From d198b4bd462c788e6aa9ca0e7a00279b0102e00d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 18 Sep 2018 09:26:47 +0200 Subject: [PATCH 0299/3726] Fix the sphinx_ipython directive. Also use it in our docs so that there is some kind of tests. Document ok-except better (and test). Work w/o matplotlib installed. Imperfect so far. Fixes gh-11320 --- IPython/core/interactiveshell.py | 4 +- IPython/sphinxext/ipython_directive.py | 103 +++++++++++++++--- docs/source/sphinxext.rst | 2 - .../pr/incompat-switching-to-perl.rst | 6 + 4 files changed, 93 insertions(+), 22 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 62bc209d1c3..c88423db238 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -81,7 +81,7 @@ from logging import error import IPython.core.hooks -from typing import List as ListType +from typing import List as ListType, Tuple from ast import AST # NoOpContext is deprecated, but ipykernel imports it from here. @@ -3287,7 +3287,7 @@ def run_code(self, code_obj, result=None, *, async_=False): # For backwards compatibility runcode = run_code - def check_complete(self, code): + def check_complete(self, code: str) -> Tuple[str, str]: """Return whether a block of code is ready to execute, or should be continued Parameters diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index f11f0cc1b5d..6fcf9d848df 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -2,12 +2,67 @@ """ Sphinx directive to support embedded IPython code. +IPython provides an extension for `Sphinx `_ to +highlight and run code. + This directive allows pasting of entire interactive IPython sessions, prompts and all, and their code will actually get re-executed at doc build time, with all prompts renumbered sequentially. It also allows you to input code as a pure python input by giving the argument python to the directive. The output looks like an interactive ipython section. +Here is an example of how the IPython directive can +**run** python code, at build time. + +.. ipython:: + + In [1]: 1+1 + + In [1]: import datetime + ...: datetime.datetime.now() + +It supports IPython construct that plain +Python does not understand (like magics): + +.. ipython:: + + In [0]: import time + + In [0]: %timeit time.sleep(0.05) + +This will also support top-level async when using IPython 7.0+ + +.. ipython:: + + In [2]: import asyncio + ...: print('before') + ...: await asyncio.sleep(1) + ...: print('after') + + +The namespace will persist across multiple code chucks, Let's define a variable: + +.. ipython:: + + In [0]: who = "World" + +And now say hello: + +.. ipython:: + + In [0]: print('Hello,', who) + +If the current section raises an exception, you can add the ``:okexcept:`` flag +to the current block, otherwise the build will fail. + +.. ipython:: + :okexcept: + + In [1]: 1/0 + +IPython Sphinx directive module +=============================== + To enable this directive, simply list it in your Sphinx ``conf.py`` file (making sure the directory where you placed it is visible to sphinx, as is needed for all Sphinx directives). For example, to enable syntax highlighting @@ -27,19 +82,19 @@ Sphinx source directory. The default is `html_static_path`. ipython_rgxin: The compiled regular expression to denote the start of IPython input - lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You + lines. The default is ``re.compile('In \[(\d+)\]:\s?(.*)\s*')``. You shouldn't need to change this. ipython_rgxout: The compiled regular expression to denote the start of IPython output - lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You + lines. The default is ``re.compile('Out\[(\d+)\]:\s?(.*)\s*')``. You shouldn't need to change this. ipython_promptin: The string to represent the IPython input prompt in the generated ReST. - The default is 'In [%d]:'. This expects that the line numbers are used + The default is ``'In [%d]:'``. This expects that the line numbers are used in the prompt. ipython_promptout: The string to represent the IPython prompt in the generated ReST. The - default is 'Out [%d]:'. This expects that the line numbers are used + default is ``'Out [%d]:'``. This expects that the line numbers are used in the prompt. ipython_mplbackend: The string which specifies if the embedded Sphinx shell should import @@ -54,7 +109,7 @@ A list of strings to be exec'd in the embedded Sphinx shell. Typical usage is to make certain packages always available. Set this to an empty list if you wish to have no imports always available. If specified in - conf.py as `None`, then it has the effect of making no imports available. + ``conf.py`` as `None`, then it has the effect of making no imports available. If omitted from conf.py altogether, then the default value of ['import numpy as np', 'import matplotlib.pyplot as plt'] is used. ipython_holdcount @@ -105,21 +160,22 @@ In [2]: # raise warning. To Do ------ +===== - Turn the ad-hoc test() function into a real test suite. - Break up ipython-specific functionality from matplotlib stuff into better separated code. -Authors -------- - -- John D Hunter: original author. -- Fernando Perez: refactoring, documentation, cleanups, port to 0.11. -- VáclavŠmilauer : Prompt generalizations. -- Skipper Seabold, refactoring, cleanups, pure python addition """ +# Authors +# ======= +# +# - John D Hunter: original author. +# - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. +# - VáclavŠmilauer : Prompt generalizations. +# - Skipper Seabold, refactoring, cleanups, pure python addition + #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- @@ -145,6 +201,14 @@ from IPython import InteractiveShell from IPython.core.profiledir import ProfileDir + +use_matpltolib = False +try: + import matplotlib + use_matpltolib = True +except Exception: + pass + #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -325,13 +389,12 @@ def clear_cout(self): def process_input_line(self, line, store_history=True): """process the input, capturing stdout""" - stdout = sys.stdout try: sys.stdout = self.cout self.lines_waiting.append(line) - if self.IP.check_complete()[0] != 'incomplete': - source_raw = ''.join(self.lines_waiting) + source_raw = ''.join(self.lines_waiting) + if self.IP.check_complete(source_raw)[0] != 'incomplete': self.lines_waiting = [] self.IP.run_cell(source_raw, store_history=store_history) finally: @@ -501,6 +564,7 @@ def process_input(self, data, input_prompt, lineno): sys.stdout.write(s) sys.stdout.write(processed_output) sys.stdout.write('<<<' + ('-' * 73) + '\n\n') + raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno)) # output any warning raised during execution to stdout # unless :okwarning: has been specified. @@ -515,6 +579,7 @@ def process_input(self, data, input_prompt, lineno): w.filename, w.lineno, w.line) sys.stdout.write(s) sys.stdout.write('<<<' + ('-' * 73) + '\n') + raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno)) self.cout.truncate(0) @@ -866,7 +931,7 @@ def setup(self): # EmbeddedSphinxShell is created, its interactive shell member # is the same for each instance. - if mplbackend and 'matplotlib.backends' not in sys.modules: + if mplbackend and 'matplotlib.backends' not in sys.modules and use_matpltolib: import matplotlib matplotlib.use(mplbackend) @@ -985,7 +1050,9 @@ def setup(app): # If the user sets this config value to `None`, then EmbeddedSphinxShell's # __init__ method will treat it as []. - execlines = ['import numpy as np', 'import matplotlib.pyplot as plt'] + execlines = ['import numpy as np'] + if use_matpltolib: + execlines.append('import matplotlib.pyplot as plt') app.add_config_value('ipython_execlines', execlines, 'env') app.add_config_value('ipython_holdcount', True, 'env') diff --git a/docs/source/sphinxext.rst b/docs/source/sphinxext.rst index 42af07bfdfe..ef000e31d50 100644 --- a/docs/source/sphinxext.rst +++ b/docs/source/sphinxext.rst @@ -2,7 +2,5 @@ IPython Sphinx extension ======================== -IPython provides an extension for `Sphinx `_ to -highlight and run code. .. automodule:: IPython.sphinxext.ipython_directive diff --git a/docs/source/whatsnew/pr/incompat-switching-to-perl.rst b/docs/source/whatsnew/pr/incompat-switching-to-perl.rst index 35f73dffe1c..ddd1d49f686 100644 --- a/docs/source/whatsnew/pr/incompat-switching-to-perl.rst +++ b/docs/source/whatsnew/pr/incompat-switching-to-perl.rst @@ -1 +1,7 @@ +Incompatible change switch to perl +---------------------------------- + +Document which filename start with ``incompat-`` will be gathers in their own +incompatibility section. + Starting with IPython 42, only perl code execution is allowed. See :ghpull:`42` From 01b0f00c11741e337481cc30a112cde1e219cc31 Mon Sep 17 00:00:00 2001 From: hongshaoyang Date: Wed, 19 Sep 2018 10:51:45 +0800 Subject: [PATCH 0300/3726] Minor edits to magics doc --- docs/source/interactive/magics.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/interactive/magics.rst b/docs/source/interactive/magics.rst index 0cc1c4f3315..bb562f44f20 100644 --- a/docs/source/interactive/magics.rst +++ b/docs/source/interactive/magics.rst @@ -5,18 +5,18 @@ Built-in magic commands .. note:: To Jupyter users: Magics are specific to and provided by the IPython kernel. - Whether magics are available on a kernel is a decision that is made by + Whether Magics are available on a kernel is a decision that is made by the kernel developer on a per-kernel basis. To work properly, Magics must use a syntax element which is not valid in the underlying language. For - example, the IPython kernel uses the `%` syntax element for magics as `%` - is not a valid unary operator in Python. While, the syntax element has - meaning in other languages. + example, the IPython kernel uses the `%` syntax element for Magics as `%` + is not a valid unary operator in Python. However, `%` might have meaning in + other languages. -Here is the help auto generated from the docstrings of all the available magics +Here is the help auto-generated from the docstrings of all the available Magics function that IPython ships with. -You can create an register your own magics with IPython. You can find many user -defined magics on `PyPI `_. Feel free to publish your own and +You can create an register your own Magics with IPython. You can find many user +defined Magics on `PyPI `_. Feel free to publish your own and use the ``Framework :: IPython`` trove classifier. From 91ed424ecf22bf0d695feda22d44c2fc4c458858 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 21 Sep 2018 09:32:59 +0200 Subject: [PATCH 0301/3726] Allow anyone to tag/untag/close issues. This is (usually) reserved to people having commit right. I'm hopping to foster participation by giving people the ability to triage issue. --- .meeseeksdev.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .meeseeksdev.yml diff --git a/.meeseeksdev.yml b/.meeseeksdev.yml new file mode 100644 index 00000000000..56c33f87084 --- /dev/null +++ b/.meeseeksdev.yml @@ -0,0 +1,17 @@ +special: + everyone: + can: + - say + - tag + - untag + - close + config: + tag: + only: + - async/await + - backported + - help wanted + - documentation + - notebook + - tab-completion + - windows From 07fe08d3de70747098a39602e1b5f965abcab04f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 21 Sep 2018 09:44:36 +0200 Subject: [PATCH 0302/3726] release 7.0.0rc1 --- IPython/core/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index e6552cff6d3..bbbe4a5a477 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -23,7 +23,7 @@ _version_minor = 0 _version_patch = 0 _version_extra = '.dev' -# _version_extra = 'b1' +_version_extra = 'rc1' # _version_extra = '' # Uncomment this for full releases # Construct full version string from these. From 032cc8c92986762204a801eed36c57822b4a6345 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 21 Sep 2018 09:45:53 +0200 Subject: [PATCH 0303/3726] back to development --- IPython/core/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index bbbe4a5a477..e6552cff6d3 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -23,7 +23,7 @@ _version_minor = 0 _version_patch = 0 _version_extra = '.dev' -_version_extra = 'rc1' +# _version_extra = 'b1' # _version_extra = '' # Uncomment this for full releases # Construct full version string from these. From 8af2a3f0f5ee7ad11b244dbbe6afb2d4031d8e3d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 23 Sep 2018 07:44:58 -0700 Subject: [PATCH 0304/3726] add info to contributing.md --- CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 752486042b3..348425622ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,31 @@ +## Triaging Issues + +On the IPython repository we strive to trust users and give them responsibility, +this is why than to one of our bot, any user can close issues, add and remove +labels by mentioning the bot and asking it to do things on your behalf. + +To close and issue (or PR), even if it is not your, use the following: + +> @meeseeksdev close + +This command can be in the middle of another comments, but must start a line. + +To add labels to an issue, as the bot to `tag` with a comma separated list of +tags to add: + +> @meeseeksdev tag windows, documentation + +Only already pre-created tags can be added, and the list is so far limitted to `async/await`, +`backported`, `help wanted`, `documentation`, `notebook`, `tab-completion`, `windows` + +To remove a label, use the `untag` command: + +> @meeseeksdev untag windows, documentation + +The list of commands that the bot can do is larger and we'll be experimenting +with what is possible. + + ## Opening an Issue When opening a new Issue, please take the following steps: From 3f86a49722008fd1d85f9c5c5f0d108ed1d70c1e Mon Sep 17 00:00:00 2001 From: hongshaoyang Date: Tue, 25 Sep 2018 22:27:07 +0800 Subject: [PATCH 0305/3726] Fix #11309 handles spaces or quotes in filename param --- IPython/core/magics/osm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 7e87b973ad2..033805ec70c 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -776,8 +776,9 @@ def writefile(self, line, cell): The file will be overwritten unless the -a (--append) flag is specified. """ + line = line if len(line.split())==1 else '"%s"' % line args = magic_arguments.parse_argstring(self.writefile, line) - filename = os.path.expanduser(args.filename) + filename = os.path.expanduser(args.filename.strip("\"\'")) if os.path.exists(filename): if args.append: From 90276eba1706957dc7e2bb48fba7d952dc6d6b50 Mon Sep 17 00:00:00 2001 From: hongshaoyang Date: Tue, 25 Sep 2018 22:40:31 +0800 Subject: [PATCH 0306/3726] Update osm.py --- IPython/core/magics/osm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 033805ec70c..f835361a4cf 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -776,7 +776,7 @@ def writefile(self, line, cell): The file will be overwritten unless the -a (--append) flag is specified. """ - line = line if len(line.split())==1 else '"%s"' % line + line = line if len(line.split())==1 else '"%s"' % line.strip("\"\'") args = magic_arguments.parse_argstring(self.writefile, line) filename = os.path.expanduser(args.filename.strip("\"\'")) From e870179290db5ccf1ced8340c136a0ce79735d8b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 25 Sep 2018 09:31:58 -0700 Subject: [PATCH 0307/3726] re-add the rprint and rprinte alias. They are used in IPykernel 4.9 and I can see users upgrading IPython w/o upgrading Ipykernel. --- IPython/utils/io.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IPython/utils/io.py b/IPython/utils/io.py index 3b518f2f54e..b59a1a11606 100644 --- a/IPython/utils/io.py +++ b/IPython/utils/io.py @@ -235,6 +235,11 @@ def raw_print_err(*args, **kw): file=sys.__stderr__) sys.__stderr__.flush() +# used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added +# Keep for a version or two then should remove +rprint = raw_print +rprinte = raw_print_err + @undoc def unicode_std_stream(stream='stdout'): """DEPRECATED, moved to nbconvert.utils.io""" From 82d5940ac2bc1a76b762156310df668c40d71412 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 26 Sep 2018 14:27:35 -0700 Subject: [PATCH 0308/3726] Take into account Carol Suggestions --- CONTRIBUTING.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 348425622ec..60a0bd2c1be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,30 +1,31 @@ ## Triaging Issues -On the IPython repository we strive to trust users and give them responsibility, -this is why than to one of our bot, any user can close issues, add and remove +On the IPython repository we strive to trust users and give them responsibility. +By using one of our bot, any user can close issues, add and remove labels by mentioning the bot and asking it to do things on your behalf. -To close and issue (or PR), even if it is not your, use the following: +To close and issue (or PR), even if you did not create it, use the following: > @meeseeksdev close -This command can be in the middle of another comments, but must start a line. +This command can be in the middle of another comments, but must start on its +own line. -To add labels to an issue, as the bot to `tag` with a comma separated list of +To add labels to an issue, ask the bot to `tag` with a comma separated list of tags to add: > @meeseeksdev tag windows, documentation -Only already pre-created tags can be added, and the list is so far limitted to `async/await`, -`backported`, `help wanted`, `documentation`, `notebook`, `tab-completion`, `windows` +Only already pre-created tags can be added, and the list is so far limited to +`async/await`, `backported`, `help wanted`, `documentation`, `notebook`, +`tab-completion`, `windows` To remove a label, use the `untag` command: > @meeseeksdev untag windows, documentation -The list of commands that the bot can do is larger and we'll be experimenting -with what is possible. - +e'll be adding additional capabilities for the bot and will share them here +when they are ready to be used. ## Opening an Issue @@ -39,8 +40,8 @@ When opening a new Issue, please take the following steps: python -c "import IPython; print(IPython.sys_info())" - And include any relevant package versions, depending on the issue, - such as matplotlib, numpy, Qt, Qt bindings (PyQt/PySide), tornado, web browser, etc. + And include any relevant package versions, depending on the issue, such as + matplotlib, numpy, Qt, Qt bindings (PyQt/PySide), tornado, web browser, etc. ## Pull Requests From cceb67250296cc5ff3cb62bc6139a9feb880211d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 07:39:17 -0700 Subject: [PATCH 0309/3726] Prepare changelog for relese --- docs/source/whatsnew/version7.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 05b8c9b7d42..1a8e1eff67c 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -7,12 +7,7 @@ IPython 7.0.0 ============= -.. warning:: - - IPython 7.0 is currently in Beta. We welcome feedback on API/changes and - addition/updates to this changelog. - -Released .... ...., 2018 +Released Thursday September 27th, 2018 IPython 7 include major features improvement as you can read in the following changelog. This is also the second major version of IPython to support only From 1535ffd8a081ff847d25d84e640b1817bcdc3499 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 07:48:09 -0700 Subject: [PATCH 0310/3726] Update what's new and stats --- docs/source/whatsnew/github-stats-7.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/source/whatsnew/github-stats-7.rst b/docs/source/whatsnew/github-stats-7.rst index 9d83a31dd3f..07146effbc8 100644 --- a/docs/source/whatsnew/github-stats-7.rst +++ b/docs/source/whatsnew/github-stats-7.rst @@ -4,15 +4,14 @@ Issues closed in the 7.x development cycle Issues closed in 7.0 -------------------- - -GitHub stats for 2018/04/02 - 2018/09/XX (tag: 7.0.0) +GitHub stats for 2018/07/29 - 2018/09/27 (since tag: 6.5.0) These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 10 issues and merged 62 pull requests. +We closed 20 issues and merged 76 pull requests. The full list can be seen `on GitHub `__ -The following 45 authors contributed 423 commits. +The following 49 authors contributed 471 commits. * alphaCTzo7G * Alyssa Whitwell @@ -30,6 +29,7 @@ The following 45 authors contributed 423 commits. * Gabriel Potter * gpotter2 * Grant Nestor +* hongshaoyang * Hugo * J Forde * Jonathan Slenders @@ -41,6 +41,7 @@ The following 45 authors contributed 423 commits. * Matthew Seal * Matthias Bussonnier * meeseeksdev[bot] +* Michael Käufl * Olesya Baranova * oscar6echo * Paul Ganssle @@ -50,6 +51,7 @@ The following 45 authors contributed 423 commits. * Shailyn javier Ortiz jimenez * Sourav Singh * Srinivas Reddy Thatiparthy +* Steven Silvester * stonebig * Subhendu Ranjan Mishra * Takafumi Arakaki @@ -57,5 +59,6 @@ The following 45 authors contributed 423 commits. * Thomas Kluyver * Todd * Wei Yen +* Yarko Tymciurak * Yutao Yuan * Zi Chong Kao From 6e37b96b9fa01c78cc529131038e1adc0343e606 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 08:02:39 -0700 Subject: [PATCH 0311/3726] release 7.0.0 --- IPython/core/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index e6552cff6d3..040948907fb 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -24,7 +24,7 @@ _version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' -# _version_extra = '' # Uncomment this for full releases +_version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 4030de4e0eb2da391f240899acdf1a70e7dd58fe Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 08:04:48 -0700 Subject: [PATCH 0312/3726] back to development --- IPython/core/release.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 040948907fb..3e69c6fea3c 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,11 +20,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 7 -_version_minor = 0 +_version_minor = 1 _version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' -_version_extra = '' # Uncomment this for full releases +# _version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 9d8832490689fe3d23a4d174ac5f7fa0376c4950 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 08:56:22 -0700 Subject: [PATCH 0313/3726] Use readthedocs.yaml file as we need a recent version of sphinx --- docs/environment.yml | 11 +++++++++++ readthedocs.yml | 4 ++++ 2 files changed, 15 insertions(+) create mode 100644 docs/environment.yml create mode 100644 readthedocs.yml diff --git a/docs/environment.yml b/docs/environment.yml new file mode 100644 index 00000000000..3ccce04ca68 --- /dev/null +++ b/docs/environment.yml @@ -0,0 +1,11 @@ +name: ipython_docs +dependencies: +- python=3.6 +- setuptools>=18.5 +- sphinx>=1.8 +- sphinx_rtd_theme +- pip: + - docrepr + - prompt_toolkit + - ipython + - ipykernel diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 00000000000..b9eadb806d6 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,4 @@ +conda: + file: docs/environment.yml +python: + version: 3 From 3772d25dfddfd7abe76b602b6c636534996e0a8c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 10:07:21 -0700 Subject: [PATCH 0314/3726] release 7.0.1 --- IPython/core/release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 3e69c6fea3c..2daaccb9514 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,11 +20,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 7 -_version_minor = 1 -_version_patch = 0 +_version_minor = 0 +_version_patch = 1 _version_extra = '.dev' # _version_extra = 'b1' -# _version_extra = '' # Uncomment this for full releases +_version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 43b46e55c5de7061d09d9657126c54ec57defb14 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 27 Sep 2018 10:08:14 -0700 Subject: [PATCH 0315/3726] back to dev --- IPython/core/release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index 2daaccb9514..3e69c6fea3c 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -20,11 +20,11 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 7 -_version_minor = 0 -_version_patch = 1 +_version_minor = 1 +_version_patch = 0 _version_extra = '.dev' # _version_extra = 'b1' -_version_extra = '' # Uncomment this for full releases +# _version_extra = '' # Uncomment this for full releases # Construct full version string from these. _ver = [_version_major, _version_minor, _version_patch] From 3b5e237f8fc96a359676d67e8d19ce9f615577a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roy=20Wellington=20=E2=85=A3?= Date: Fri, 28 Sep 2018 11:49:03 -0700 Subject: [PATCH 0316/3726] Touch up the grammar & wording on the shortcuts documentation --- docs/source/config/shortcuts/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/config/shortcuts/index.rst b/docs/source/config/shortcuts/index.rst index 29088f6d19e..4103d92a7bd 100755 --- a/docs/source/config/shortcuts/index.rst +++ b/docs/source/config/shortcuts/index.rst @@ -2,12 +2,12 @@ IPython shortcuts ================= -Available shortcut in IPython terminal. +Available shortcuts in an IPython terminal. .. warning:: - This list is automatically generated, and may not hold all the available - shortcut. In particular, it may depends on the version of ``prompt_toolkit`` + This list is automatically generated, and may not hold all available + shortcuts. In particular, it may depend on the version of ``prompt_toolkit`` installed during the generation of this page. @@ -22,7 +22,7 @@ Single Filtered shortcuts Multi Filtered shortcuts -========================= +======================== .. csv-table:: :header: Shortcut,Filter,Description From 84f64e5856817cf8923d0b02f20febda3ce646a0 Mon Sep 17 00:00:00 2001 From: Emil Hessman Date: Sat, 29 Sep 2018 19:00:51 +0200 Subject: [PATCH 0317/3726] Avoid modifying mutable default value --- IPython/extensions/autoreload.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 306bb8bd26a..ca6be10f35c 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -338,7 +338,7 @@ def __call__(self): return self.obj -def superreload(module, reload=reload, old_objects={}): +def superreload(module, reload=reload, old_objects=None): """Enhanced version of the builtin reload function. superreload remembers objects previously in the module, and @@ -348,6 +348,8 @@ def superreload(module, reload=reload, old_objects={}): - clears the module's namespace before reloading """ + if old_objects is None: + old_objects = {} # collect old objects in the module for name, obj in list(module.__dict__.items()): From 65eaef68a5a2689c60a38daf09ea4b3a7a2bc599 Mon Sep 17 00:00:00 2001 From: Michael Penkov Date: Mon, 1 Oct 2018 21:38:28 +0900 Subject: [PATCH 0318/3726] Fix #9343: warn when using HTML instead of IFrame --- IPython/core/display.py | 5 +++++ IPython/core/tests/test_display.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/IPython/core/display.py b/IPython/core/display.py index ca5c3aa5b86..4a2e2817910 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -666,6 +666,11 @@ def _repr_pretty_(self, pp, cycle): class HTML(TextDisplayObject): + def __init__(self, data=None, url=None, filename=None, metadata=None): + if data and "') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + def test_progress(): p = display.ProgressBar(10) nt.assert_in('0/10',repr(p)) From 19c262a992dbee6d9250e3b117b5d4e1a5820828 Mon Sep 17 00:00:00 2001 From: Michael Penkov Date: Mon, 1 Oct 2018 22:15:36 +0900 Subject: [PATCH 0319/3726] Fix #9227: add documentation to integration tutorial --- docs/source/config/integrating.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/config/integrating.rst b/docs/source/config/integrating.rst index 1da4960700f..ded720fee19 100644 --- a/docs/source/config/integrating.rst +++ b/docs/source/config/integrating.rst @@ -65,6 +65,21 @@ There are also two more powerful display methods: Displays the object as a side effect; the return value is ignored. If this is defined, all other display methods are ignored. +To customize how the REPL pretty-prints your object, add a `_repr_pretty_` +method to the class. The method should accept a pretty printer, and a boolean +that indicates whether the printer detected a cycle. The method should act on +the printer to produce your customized pretty output. Here is an example:: + + class MyObject(object): + + def _repr_pretty_(self, p, cycle): + if cycle: + p.text('MyObject(...)') + else: + p.text('MyObject[...]') + +For details, see :py:mod:`IPython.lib.pretty`. + Formatters for third-party types -------------------------------- From b1afef3a0567cd2831a18c584973bc99a68a6244 Mon Sep 17 00:00:00 2001 From: Michael Penkov Date: Mon, 1 Oct 2018 22:37:18 +0900 Subject: [PATCH 0320/3726] Fix #10973: improve documentation for _repr_ functions --- IPython/core/display.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index ca5c3aa5b86..8cecd71b7a7 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -238,16 +238,22 @@ def display(*objs, include=None, exclude=None, metadata=None, transient=None, di want to use. Here is a list of the names of the special methods and the values they must return: - - `_repr_html_`: return raw HTML as a string - - `_repr_json_`: return a JSONable dict - - `_repr_jpeg_`: return raw JPEG data - - `_repr_png_`: return raw PNG data - - `_repr_svg_`: return raw SVG data as a string - - `_repr_latex_`: return LaTeX commands in a string surrounded by "$". + - `_repr_html_`: return raw HTML as a string, or a tuple (see below). + - `_repr_json_`: return a JSONable dict, or a tuple (see below). + - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below). + - `_repr_png_`: return raw PNG data, or a tuple (see below). + - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below). + - `_repr_latex_`: return LaTeX commands in a string surrounded by "$", + or a tuple (see below). - `_repr_mimebundle_`: return a full mimebundle containing the mapping from all mimetypes to data. Use this for any mime-type not listed above. + The above functions may also return the object's metadata alonside the + data. If the metadata is available, the functions will return a tuple + containing the data and metadata, in that order. If there is no metadata + available, then the functions will return the data only. + When you are directly writing your own classes, you can adapt them for display in IPython by following the above approach. But in practice, you often need to work with existing classes that you can't easily modify. From 211ca17805a5febfbeb08abb242b523d862c568c Mon Sep 17 00:00:00 2001 From: Dominic Kuang Date: Mon, 1 Oct 2018 13:42:44 -0700 Subject: [PATCH 0321/3726] Add support for width and height arguments when displaying Video Closes #11328 --- IPython/core/display.py | 27 ++++++++++++++----- .../source/whatsnew/pr/video-width-height.rst | 1 + 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 docs/source/whatsnew/pr/video-width-height.rst diff --git a/IPython/core/display.py b/IPython/core/display.py index ca5c3aa5b86..4123c4d69f5 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1256,7 +1256,8 @@ def _find_ext(self, s): class Video(DisplayObject): - def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None): + def __init__(self, data=None, url=None, filename=None, embed=False, + mimetype=None, width=None, height=None): """Create a video object given raw data or an URL. When this object is returned by an input cell or passed to the @@ -1288,6 +1289,12 @@ def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=Non mimetype: unicode Specify the mimetype for embedded videos. Default will be guessed from file extension, if available. + width : int + Width in pixels to which to constrain the video in HTML. + If not supplied, defaults to the width of the video. + height : int + Height in pixels to which to constrain the video in html. + If not supplied, defaults to the height of the video. Examples -------- @@ -1314,16 +1321,24 @@ def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=Non self.mimetype = mimetype self.embed = embed + self.width = width + self.height = height super(Video, self).__init__(data=data, url=url, filename=filename) def _repr_html_(self): + width = height = '' + if self.width: + width = ' width="%d"' % self.width + if self.height: + height = ' height="%d"' % self.height + # External URLs and potentially local files are not embedded into the # notebook output. if not self.embed: url = self.url if self.url is not None else self.filename - output = """""".format(url, width, height) return output # Embedded videos are base64-encoded. @@ -1342,10 +1357,10 @@ def _repr_html_(self): else: b64_video = b2a_base64(video).decode('ascii').rstrip() - output = """""".format(width, height, mimetype, b64_video) return output def reload(self): diff --git a/docs/source/whatsnew/pr/video-width-height.rst b/docs/source/whatsnew/pr/video-width-height.rst new file mode 100644 index 00000000000..84757f1b430 --- /dev/null +++ b/docs/source/whatsnew/pr/video-width-height.rst @@ -0,0 +1 @@ +``IPython.display.Video`` now supports ``width`` and ``height`` arguments, allowing a custom width and height to be set instead of using the video's width and height \ No newline at end of file From a6c064a0825fe6698eb624dbba564c2d5f3a5803 Mon Sep 17 00:00:00 2001 From: Bart Skowron Date: Tue, 2 Oct 2018 00:34:38 +0200 Subject: [PATCH 0322/3726] Fix #11334: Cannot make multi-line code blocks in ipython When codeop.compile_command() returns None it actually says "at least some part of the code was compiled successfully" which is not really important for checking if it's complete or not. Once we haven't got any errors during compilation process, we just want to check if there will be another nested block of code or not by checking a colon. --- IPython/core/inputtransformer2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 1abd2cf95cb..3c992d81046 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -636,7 +636,7 @@ def check_complete(self, cell: str): MemoryError, SyntaxWarning): return 'invalid', None else: - if res is None: + if not lines[-1].strip().endswith(':'): return 'incomplete', find_last_indent(lines) return 'complete', None From 0a42b8611675af719577fa2fa85ea9ddfa720452 Mon Sep 17 00:00:00 2001 From: Bart Skowron Date: Tue, 2 Oct 2018 01:30:28 +0200 Subject: [PATCH 0323/3726] Fix regression... --- IPython/core/inputtransformer2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 3c992d81046..63cf1d77dee 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -636,7 +636,7 @@ def check_complete(self, cell: str): MemoryError, SyntaxWarning): return 'invalid', None else: - if not lines[-1].strip().endswith(':'): + if len(lines) > 1 and not lines[-1].strip().endswith(':'): return 'incomplete', find_last_indent(lines) return 'complete', None From fde3f770a84f1815a47aa5f5a05c0622a7867b1a Mon Sep 17 00:00:00 2001 From: Bart Skowron Date: Tue, 2 Oct 2018 02:10:35 +0200 Subject: [PATCH 0324/3726] Add a new regression test and fix for it --- IPython/core/inputtransformer2.py | 3 ++- IPython/core/tests/test_inputtransformer2.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 63cf1d77dee..afe0e002dff 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -636,7 +636,8 @@ def check_complete(self, cell: str): MemoryError, SyntaxWarning): return 'invalid', None else: - if len(lines) > 1 and not lines[-1].strip().endswith(':'): + if len(lines) > 1 and not lines[-1].strip().endswith(':') \ + and not lines[-2][:-1].endswith('\\'): return 'incomplete', find_last_indent(lines) return 'complete', None diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index f78a0b37158..d1ef3dff42b 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -206,6 +206,7 @@ def test_check_complete(): nt.assert_equal(cc("a = '''\n hi"), ('incomplete', 3)) nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None)) nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash + nt.assert_equal(cc("1\\\n+2"), ('complete', None)) # no need to loop on all the letters/numbers. short = '12abAB'+string.printable[62:] From d0cad1f760c8e203dc4930fe6d2dffe1baeb33eb Mon Sep 17 00:00:00 2001 From: Bart Skowron Date: Tue, 2 Oct 2018 02:11:06 +0200 Subject: [PATCH 0325/3726] Remove unused assignment --- IPython/core/inputtransformer2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index afe0e002dff..ac76f05e93c 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -631,7 +631,7 @@ def check_complete(self, cell: str): try: with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) - res = compile_command(''.join(lines), symbol='exec') + compile_command(''.join(lines), symbol='exec') except (SyntaxError, OverflowError, ValueError, TypeError, MemoryError, SyntaxWarning): return 'invalid', None From d4f41859f3d9080e99ac607a7f589e67c9805fd3 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Tue, 2 Oct 2018 00:03:58 -0400 Subject: [PATCH 0326/3726] Fix an IndexError in leading_indent --- IPython/core/inputtransformer2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 1abd2cf95cb..c9aff48a53a 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -24,6 +24,8 @@ def leading_indent(lines): If the first line starts with a spaces or tabs, the same whitespace will be removed from each following line in the cell. """ + if not lines: + return lines m = _indent_re.match(lines[0]) if not m: return lines From 8b13c4b7353c3a2bdcddd27907ba3a50202baaec Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Tue, 2 Oct 2018 14:14:45 -0400 Subject: [PATCH 0327/3726] Add tests for null cleanup test. --- IPython/core/tests/test_inputtransformer2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index f78a0b37158..77df22c2c22 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -101,6 +101,12 @@ [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] ) +def null_cleanup_transformer(lines): + """ + A cleanup transform that returns an empty list. + """ + return [] + def check_make_token_by_line_never_ends_empty(): """ Check that not sequence of single or double characters ends up leading to en empty list of tokens @@ -215,3 +221,7 @@ def test_check_complete(): for k in short: cc(c+k) +def test_null_cleanup_transformer(): + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, null_cleanup_transformer) + nt.assert_is(manager.transform_cell(""), "") From 5b3ce918a77bb16ec52b3097f4b3611b8847fa95 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Tue, 2 Oct 2018 14:14:57 -0400 Subject: [PATCH 0328/3726] Include empty lines condition in PromptStipper and cell_magic. --- IPython/core/inputtransformer2.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index c9aff48a53a..0a01100398d 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -20,11 +20,11 @@ def leading_indent(lines): """Remove leading indentation. - + If the first line starts with a spaces or tabs, the same whitespace will be removed from each following line in the cell. """ - if not lines: + if not lines: return lines m = _indent_re.match(lines[0]) if not m: @@ -36,7 +36,7 @@ def leading_indent(lines): class PromptStripper: """Remove matching input prompts from a block of input. - + Parameters ---------- prompt_re : regular expression @@ -47,7 +47,7 @@ class PromptStripper: If no initial expression is given, prompt_re will be used everywhere. Used mainly for plain Python prompts (``>>>``), where the continuation prompt ``...`` is a valid Python expression in Python 3, so shouldn't be stripped. - + If initial_re and prompt_re differ, only initial_re will be tested against the first line. If any prompt is found on the first two lines, @@ -61,6 +61,8 @@ def _strip(self, lines): return [self.prompt_re.sub('', l, count=1) for l in lines] def __call__(self, lines): + if not lines: + return lines if self.initial_re.match(lines[0]) or \ (len(lines) > 1 and self.prompt_re.match(lines[1])): return self._strip(lines) @@ -74,7 +76,7 @@ def __call__(self, lines): ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')) def cell_magic(lines): - if not lines[0].startswith('%%'): + if not lines or not lines[0].startswith('%%'): return lines if re.match('%%\w+\?', lines[0]): # This case will be handled by help_end @@ -94,7 +96,7 @@ def _find_assign_op(token_line): for i, ti in enumerate(token_line): s = ti.string if s == '=' and paren_level == 0: - return i + return i if s in '([{': paren_level += 1 elif s in ')]}': @@ -114,7 +116,7 @@ def find_end_of_continued_line(lines, start_line: int): return end_line def assemble_continued_line(lines, start: Tuple[int, int], end_line: int): - """Assemble a single line from multiple continued line pieces + """Assemble a single line from multiple continued line pieces Continued lines are lines ending in ``\``, and the line following the last ``\`` in the block. @@ -204,7 +206,7 @@ def find(cls, tokens_by_line): and (line[assign_ix+1].string == '%') \ and (line[assign_ix+2].type == tokenize.NAME): return cls(line[assign_ix+1].start) - + def transform(self, lines: List[str]): """Transform a magic assignment found by the ``find()`` classmethod. """ @@ -214,12 +216,12 @@ def transform(self, lines: List[str]): rhs = assemble_continued_line(lines, (start_line, start_col), end_line) assert rhs.startswith('%'), rhs magic_name, _, args = rhs[1:].partition(' ') - + lines_before = lines[:start_line] call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args) new_line = lhs + call + '\n' lines_after = lines[end_line+1:] - + return lines_before + [new_line] + lines_after @@ -466,7 +468,7 @@ def make_tokens_by_line(lines): pass if not tokens_by_line[-1]: tokens_by_line.pop() - + return tokens_by_line def show_linewise_tokens(s: str): @@ -503,12 +505,12 @@ def __init__(self): EscapedCommand, HelpEnd, ] - + def do_one_token_transform(self, lines): """Find and run the transform earliest in the code. - + Returns (changed, lines). - + This method is called repeatedly until changed is False, indicating that all available transformations are complete. From 5130108723256e656cc4f242e27f3164e28719f1 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 20 Sep 2018 18:29:19 +0200 Subject: [PATCH 0329/3726] Math display: change $$...$$ -> $\displaystyle ...$ --- IPython/core/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 8cecd71b7a7..8d386277e9a 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -693,7 +693,7 @@ def _repr_markdown_(self): class Math(TextDisplayObject): def _repr_latex_(self): - s = "$$%s$$" % self.data.strip('$') + s = "$\displaystyle %s$" % self.data.strip('$') if self.metadata: return s, deepcopy(self.metadata) else: From 043b67794072c99746677d9431e97b01551c21ff Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 3 Oct 2018 10:29:46 -0700 Subject: [PATCH 0330/3726] add tests --- IPython/core/tests/test_inputtransformer2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index d1ef3dff42b..28e0458fc20 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -10,6 +10,8 @@ from IPython.core import inputtransformer2 as ipt2 from IPython.core.inputtransformer2 import make_tokens_by_line +from textwrap import dedent + MULTILINE_MAGIC = ("""\ a = f() %foo \\ @@ -208,6 +210,14 @@ def test_check_complete(): nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash nt.assert_equal(cc("1\\\n+2"), ('complete', None)) + example = dedent(""" + if True: + a=1""" ) + + nt.assert_equal(cc(example), ('incomplete', 4)) + nt.assert_equal(cc(example+'\n'), ('complete', None)) + nt.assert_equal(cc(example+'\n '), ('complete', None)) + # no need to loop on all the letters/numbers. short = '12abAB'+string.printable[62:] for c in short: From 498f5f716d102a572cac3571e13426aa002f8454 Mon Sep 17 00:00:00 2001 From: Bart Skowron Date: Thu, 4 Oct 2018 00:26:28 +0200 Subject: [PATCH 0331/3726] always add a trailing newline --- IPython/core/inputtransformer2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index ac76f05e93c..2c44ee11c01 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -571,8 +571,7 @@ def check_complete(self, cell: str): The number of spaces by which to indent the next line of code. If status is not 'incomplete', this is None. """ - if not cell.endswith('\n'): - cell += '\n' # Ensure the cell has a trailing newline + cell += '\n' # Ensure the cell has a trailing newline lines = cell.splitlines(keepends=True) if lines[-1][:-1].endswith('\\'): # Explicit backslash continuation From 186524bc08716d9457d0a4c45d2d33fb58482427 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Wed, 3 Oct 2018 21:53:13 -0400 Subject: [PATCH 0332/3726] Add some logic to pass all of the check_complete tests An assert statement needed to be commented out. I'm not sure what the statement was stating though. --- IPython/core/inputtransformer2.py | 49 ++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 503bbc1b321..d53f702a9a4 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -253,7 +253,7 @@ def transform(self, lines: List[str]): lhs = lines[start_line][:start_col] end_line = find_end_of_continued_line(lines, start_line) rhs = assemble_continued_line(lines, (start_line, start_col), end_line) - assert rhs.startswith('!'), rhs + # assert rhs.startswith('!'), rhs cmd = rhs[1:] lines_before = lines[:start_line] @@ -369,11 +369,15 @@ def transform(self, lines): end_line = find_end_of_continued_line(lines, start_line) line = assemble_continued_line(lines, (start_line, start_col), end_line) - if line[:2] in ESCAPE_DOUBLES: + if len(line) > 1 and line[:2] in ESCAPE_DOUBLES: escape, content = line[:2], line[2:] else: escape, content = line[:1], line[1:] - call = tr[escape](content) + + if escape in tr: + call = tr[escape](content) + else: + call = '' lines_before = lines[:start_line] new_line = indent + call + '\n' @@ -575,9 +579,11 @@ def check_complete(self, cell: str): The number of spaces by which to indent the next line of code. If status is not 'incomplete', this is None. """ - cell += '\n' # Ensure the cell has a trailing newline lines = cell.splitlines(keepends=True) - if lines[-1][:-1].endswith('\\'): + if not lines: + return 'complete', None + + if lines[-1].endswith('\\'): # Explicit backslash continuation return 'incomplete', find_last_indent(lines) @@ -604,44 +610,53 @@ def check_complete(self, cell: str): tokens_by_line = make_tokens_by_line(lines) if not tokens_by_line: return 'incomplete', find_last_indent(lines) + if tokens_by_line[-1][-1].type != tokenize.ENDMARKER: # We're in a multiline string or expression return 'incomplete', find_last_indent(lines) - if len(tokens_by_line) == 1: + + if len(tokens_by_line[-1]) == 1: return 'incomplete', find_last_indent(lines) # Find the last token on the previous line that's not NEWLINE or COMMENT - toks_last_line = tokens_by_line[-2] - ix = len(toks_last_line) - 1 - while ix >= 0 and toks_last_line[ix].type in {tokenize.NEWLINE, + toks_last_line = tokens_by_line[-1] + ix = len(tokens_by_line) - 1 + + + while ix >= 0 and toks_last_line[-1].type in {tokenize.NEWLINE, tokenize.COMMENT}: ix -= 1 - - if toks_last_line[ix].string == ':': + if tokens_by_line[ix][-2].string == ':': # The last line starts a block (e.g. 'if foo:') ix = 0 while toks_last_line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: ix += 1 indent = toks_last_line[ix].start[1] return 'incomplete', indent + 4 + if tokens_by_line[ix][-2].string == '\\': + if not tokens_by_line[ix][-2].line.endswith('\\'): + return 'invalid', None - # If there's a blank line at the end, assume we're ready to execute. + # If there's a blank line at the end, assume we're ready to execute if not lines[-1].strip(): return 'complete', None # At this point, our checks think the code is complete (or invalid). - # We'll use codeop.compile_command to check this with the real parser. - + # We'll use codeop.compile_command to check this with the real parser try: with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) - compile_command(''.join(lines), symbol='exec') + res = compile_command(''.join(lines), symbol='exec') except (SyntaxError, OverflowError, ValueError, TypeError, MemoryError, SyntaxWarning): return 'invalid', None else: - if len(lines) > 1 and not lines[-1].strip().endswith(':') \ - and not lines[-2][:-1].endswith('\\'): + if res is None: return 'incomplete', find_last_indent(lines) + + if toks_last_line[-2].type == tokenize.DEDENT: + if not lines[-1].endswith('\n'): + return 'incomplete', find_last_indent(lines) + return 'complete', None From 51d59bbbbe086ed93bbce430190a8cccfec17630 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Wed, 3 Oct 2018 23:46:06 -0400 Subject: [PATCH 0333/3726] Use tokenize.NL and tokenize.NEWLINE in the check_complete logic These changes are necessary for a failure on Python 3.7 dev https://bugs.python.org/issue33899 --- IPython/core/inputtransformer2.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index d53f702a9a4..64813ec7144 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -653,6 +653,9 @@ def check_complete(self, cell: str): if res is None: return 'incomplete', find_last_indent(lines) + if toks_last_line[-2].type in {tokenize.NEWLINE, tokenize.NL}: + return 'complete', None + if toks_last_line[-2].type == tokenize.DEDENT: if not lines[-1].endswith('\n'): return 'incomplete', find_last_indent(lines) From 4fdf684c44f2e0e087d064399dd33014ab1061c1 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Thu, 4 Oct 2018 10:28:41 -0400 Subject: [PATCH 0334/3726] Add 3.6-dev to the test. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 09e16146476..cf184a9d993 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ after_success: matrix: include: + - { python: "3.6-dev", dist: xenial, sudo: true } - { python: "3.7", dist: xenial, sudo: true } - { python: "3.7-dev", dist: xenial, sudo: true } - { python: "nightly", dist: xenial, sudo: true } From 06f2f2d39feb67e1cf92d0fe75150c3e2423562f Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Thu, 4 Oct 2018 23:30:51 -0400 Subject: [PATCH 0335/3726] A refactor to check_complete to pass the test cases. --- IPython/core/inputtransformer2.py | 69 +++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 64813ec7144..7be401f1175 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -470,9 +470,12 @@ def make_tokens_by_line(lines): except tokenize.TokenError: # Input ended in a multiline string or expression. That's OK for us. pass + + if not tokens_by_line[-1]: tokens_by_line.pop() + return tokens_by_line def show_linewise_tokens(s: str): @@ -579,7 +582,24 @@ def check_complete(self, cell: str): The number of spaces by which to indent the next line of code. If status is not 'incomplete', this is None. """ + # Remember if the lines ends in a new line. + ends_with_newline = False + for character in reversed(cell): + if character == '\n': + ends_with_newline = True + break + elif character.strip(): + break + else: + continue + + if ends_with_newline: + # Append an newline for consistent tokenization + # See https://bugs.python.org/issue33899 + cell += '\n' + lines = cell.splitlines(keepends=True) + if not lines: return 'complete', None @@ -608,6 +628,7 @@ def check_complete(self, cell: str): return 'invalid', None tokens_by_line = make_tokens_by_line(lines) + if not tokens_by_line: return 'incomplete', find_last_indent(lines) @@ -615,30 +636,33 @@ def check_complete(self, cell: str): # We're in a multiline string or expression return 'incomplete', find_last_indent(lines) - if len(tokens_by_line[-1]) == 1: - return 'incomplete', find_last_indent(lines) - # Find the last token on the previous line that's not NEWLINE or COMMENT - toks_last_line = tokens_by_line[-1] - ix = len(tokens_by_line) - 1 + newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} + + # Remove newline_types for the list of tokens + while len(tokens_by_line) > 1 and len(tokens_by_line[-1]) == 1 \ + and tokens_by_line[-1][-1].type in newline_types: + tokens_by_line.pop() + last_line_token = tokens_by_line[-1] - while ix >= 0 and toks_last_line[-1].type in {tokenize.NEWLINE, - tokenize.COMMENT}: - ix -= 1 - if tokens_by_line[ix][-2].string == ':': + while tokens_by_line[-1][-1].type in newline_types: + last_line_token = tokens_by_line[-1].pop() + + if len(last_line_token) == 1 and not last_line_token[-1]: + return 'incomplete', 0 + + if last_line_token[-1].string == ':': # The last line starts a block (e.g. 'if foo:') ix = 0 - while toks_last_line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: + while last_line_token[ix].type \ + in {tokenize.INDENT, tokenize.DEDENT}: ix += 1 - indent = toks_last_line[ix].start[1] + + indent = last_line_token[ix].start[1] return 'incomplete', indent + 4 - if tokens_by_line[ix][-2].string == '\\': - if not tokens_by_line[ix][-2].line.endswith('\\'): - return 'invalid', None - # If there's a blank line at the end, assume we're ready to execute - if not lines[-1].strip(): - return 'complete', None + if last_line_token[-1].line.endswith('\\'): + return 'incomplete', None # At this point, our checks think the code is complete (or invalid). # We'll use codeop.compile_command to check this with the real parser @@ -653,12 +677,13 @@ def check_complete(self, cell: str): if res is None: return 'incomplete', find_last_indent(lines) - if toks_last_line[-2].type in {tokenize.NEWLINE, tokenize.NL}: - return 'complete', None + if last_line_token[-1].type == tokenize.DEDENT: + if ends_with_newline: + return 'complete', None + return 'incomplete', find_last_indent(lines) - if toks_last_line[-2].type == tokenize.DEDENT: - if not lines[-1].endswith('\n'): - return 'incomplete', find_last_indent(lines) + if len(last_line_token) <= 1: + return 'incomplete', find_last_indent(lines) return 'complete', None From 246b492ec30bd240d58f03dc2f6176e3f0a1c213 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Thu, 4 Oct 2018 23:43:49 -0400 Subject: [PATCH 0336/3726] Fix check_complete with a more verbose approach. --- IPython/core/inputtransformer2.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 7be401f1175..fd7bc95a596 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -643,25 +643,23 @@ def check_complete(self, cell: str): and tokens_by_line[-1][-1].type in newline_types: tokens_by_line.pop() - last_line_token = tokens_by_line[-1] - while tokens_by_line[-1][-1].type in newline_types: - last_line_token = tokens_by_line[-1].pop() + while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types: + tokens_by_line[-1].pop() - if len(last_line_token) == 1 and not last_line_token[-1]: + if len(tokens_by_line) == 1 and not tokens_by_line[-1]: return 'incomplete', 0 - if last_line_token[-1].string == ':': + if tokens_by_line[-1][-1].string == ':': # The last line starts a block (e.g. 'if foo:') ix = 0 - while last_line_token[ix].type \ - in {tokenize.INDENT, tokenize.DEDENT}: + while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}: ix += 1 - indent = last_line_token[ix].start[1] + indent = tokens_by_line[-1][ix].start[1] return 'incomplete', indent + 4 - if last_line_token[-1].line.endswith('\\'): + if tokens_by_line[-1][0].line.endswith('\\'): return 'incomplete', None # At this point, our checks think the code is complete (or invalid). @@ -677,14 +675,18 @@ def check_complete(self, cell: str): if res is None: return 'incomplete', find_last_indent(lines) - if last_line_token[-1].type == tokenize.DEDENT: + if tokens_by_line[-1][-1].type == tokenize.DEDENT: if ends_with_newline: return 'complete', None return 'incomplete', find_last_indent(lines) - if len(last_line_token) <= 1: + if len(tokens_by_line[-1]) <= 1: return 'incomplete', find_last_indent(lines) + # If there's a blank line at the end, assume we're ready to execute + if not lines[-1].strip(): + return 'complete', None + return 'complete', None From 0407e91d9085587b84af622d15e48d5183e62a91 Mon Sep 17 00:00:00 2001 From: Kory Donati Date: Fri, 5 Oct 2018 14:01:43 -0700 Subject: [PATCH 0337/3726] added scandir and fixed how isexec is created --- IPython/core/magics/osm.py | 116 ++++++++++++++++++++++++------------- IPython/core/profileapp.py | 21 +++---- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 7e87b973ad2..2bf8938977d 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -24,6 +24,7 @@ from IPython.utils.openpy import source_to_unicode from IPython.utils.process import abbrev_cwd from IPython.utils.terminal import set_term_title +from os import DirEntry @magics_class @@ -31,6 +32,51 @@ class OSMagics(Magics): """Magics to interact with the underlying OS (shell-type functionality). """ + def __init__(self, shell=None, **kwargs): + + # Now define isexec in a cross platform manner. + self.is_posix: bool = False + self.execre = None + if os.name == 'posix': + self.is_posix = True + else: + try: + winext = os.environ['pathext'].replace(';','|').replace('.','') + except KeyError: + winext = 'exe|com|bat|py' + + self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) + + # call up the chain + super(OSMagics, self).__init__(shell=shell, **kwargs) + + + @skip_doctest + def _isexec_POSIX(self, f:DirEntry) -> bool: + """ + Test for executible on a POSIX system + """ + return f.is_file() and os.access(f.path, os.X_OK) + + + @skip_doctest + def _isexec_WIN(self, f:DirEntry) -> int: + """ + Test for executible file on non POSIX system + """ + return f.is_file() and self.execre.match(f.name) is not None + + @skip_doctest + def isexec(self, f:DirEntry) -> bool: + """ + Test for executible file on non POSIX system + """ + if self.is_posix: + return self._isexec_POSIX(f) + else: + return self._isexec_WIN(f) + + @skip_doctest @line_magic def alias(self, parameter_s=''): @@ -160,19 +206,6 @@ def rehashx(self, parameter_s=''): os.environ.get('PATH','').split(os.pathsep)] syscmdlist = [] - # Now define isexec in a cross platform manner. - if os.name == 'posix': - isexec = lambda fname:os.path.isfile(fname) and \ - os.access(fname,os.X_OK) - else: - try: - winext = os.environ['pathext'].replace(';','|').replace('.','') - except KeyError: - winext = 'exe|com|bat|py' - if 'py' not in winext: - winext += '|py' - execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) - isexec = lambda fname:os.path.isfile(fname) and execre.match(fname) savedir = os.getcwd() # Now walk the paths looking for executables to alias. @@ -183,42 +216,44 @@ def rehashx(self, parameter_s=''): for pdir in path: try: os.chdir(pdir) - dirlist = os.listdir(pdir) except OSError: continue - for ff in dirlist: - if isexec(ff): - try: - # Removes dots from the name since ipython - # will assume names with dots to be python. - if not self.shell.alias_manager.is_alias(ff): - self.shell.alias_manager.define_alias( - ff.replace('.',''), ff) - except InvalidAliasError: - pass - else: - syscmdlist.append(ff) + with os.scandir(pdir) as dirlist: + for ff in dirlist: + if self.isexec(ff): + fname = ff.name + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + if not self.shell.alias_manager.is_alias(fname): + self.shell.alias_manager.define_alias( + fname.replace('.',''), fname) + except InvalidAliasError: + pass + else: + syscmdlist.append(fname) else: no_alias = Alias.blacklist for pdir in path: try: os.chdir(pdir) - dirlist = os.listdir(pdir) except OSError: continue - for ff in dirlist: - base, ext = os.path.splitext(ff) - if isexec(ff) and base.lower() not in no_alias: - if ext.lower() == '.exe': - ff = base - try: - # Removes dots from the name since ipython - # will assume names with dots to be python. - self.shell.alias_manager.define_alias( - base.lower().replace('.',''), ff) - except InvalidAliasError: - pass - syscmdlist.append(ff) + with os.scandir(pdir) as dirlist: + for ff in dirlist: + fname = ff.name + base, ext = os.path.splitext(fname) + if self.isexec(ff) and base.lower() not in no_alias: + if ext.lower() == '.exe': + fname = base + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + self.shell.alias_manager.define_alias( + base.lower().replace('.',''), fname) + except InvalidAliasError: + pass + syscmdlist.append(fname) self.shell.db['syscmdlist'] = syscmdlist finally: os.chdir(savedir) @@ -481,6 +516,7 @@ def dhist(self, parameter_s=''): dh = self.shell.user_ns['_dh'] if parameter_s: + args = [] try: args = map(int,parameter_s.split()) except: diff --git a/IPython/core/profileapp.py b/IPython/core/profileapp.py index 59282a2c111..700fcc7a008 100644 --- a/IPython/core/profileapp.py +++ b/IPython/core/profileapp.py @@ -96,27 +96,22 @@ def list_profiles_in(path): """list profiles in a given root directory""" - files = os.listdir(path) profiles = [] - for f in files: - try: - full_path = os.path.join(path, f) - except UnicodeError: - continue - if os.path.isdir(full_path) and f.startswith('profile_'): - profiles.append(f.split('_',1)[-1]) + with os.scandir(path) as files: + for f in files: + if f.is_dir() and f.name.startswith('profile_'): + profiles.append(f.name.split('_', 1)[-1]) return profiles def list_bundled_profiles(): """list profiles that are bundled with IPython.""" path = os.path.join(get_ipython_package_dir(), u'core', u'profile') - files = os.listdir(path) profiles = [] - for profile in files: - full_path = os.path.join(path, profile) - if os.path.isdir(full_path) and profile != "__pycache__": - profiles.append(profile) + with os.scandir(path) as files: + for profile in files: + if profile.is_dir() and profile.name != "__pycache__": + profiles.append(profile.name) return profiles From c11265e8456892eb8a2e0cf3b825127eab52c530 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sat, 6 Oct 2018 10:41:45 -0400 Subject: [PATCH 0338/3726] Uncomment the ! assertion. Co-Authored-By: Nicholas Bollweg --- IPython/core/inputtransformer2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index fd7bc95a596..423c0fbf772 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -253,7 +253,7 @@ def transform(self, lines: List[str]): lhs = lines[start_line][:start_col] end_line = find_end_of_continued_line(lines, start_line) rhs = assemble_continued_line(lines, (start_line, start_col), end_line) - # assert rhs.startswith('!'), rhs + assert rhs.startswith('!'), rhs cmd = rhs[1:] lines_before = lines[:start_line] From f7642d4ecdcf172e6bcee5e6d8f23c259f8637c1 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sat, 6 Oct 2018 11:47:04 -0400 Subject: [PATCH 0339/3726] Add an extra condition to SystemAssign. Co-Authored-By: Nicholas Bollweg --- IPython/core/inputtransformer2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 423c0fbf772..77b905c465d 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -234,6 +234,7 @@ def find(cls, tokens_by_line): for line in tokens_by_line: assign_ix = _find_assign_op(line) if (assign_ix is not None) \ + and not line[assign_ix].line.strip().startswith('=') \ and (len(line) >= assign_ix + 2) \ and (line[assign_ix + 1].type == tokenize.ERRORTOKEN): ix = assign_ix + 1 From 76599aed8f53e36c0ba30bb2f77433362ec24666 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sat, 6 Oct 2018 12:18:39 -0400 Subject: [PATCH 0340/3726] Add check_complete test for exit. Co-Authored-By: Nicholas Bollweg --- IPython/core/tests/test_inputtransformer2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index 839fe0e16c4..9c5a413955f 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -215,6 +215,8 @@ def test_check_complete(): nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None)) nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash nt.assert_equal(cc("1\\\n+2"), ('complete', None)) + nt.assert_equal(cc("1\\\n+2"), ('complete', None)) + nt.assert_equal(cc("exit"), ('complete', None)) example = dedent(""" if True: From bd7a4c2d4abf3e5b765cfc04eb7fde67fa6822ea Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sat, 6 Oct 2018 12:32:44 -0400 Subject: [PATCH 0341/3726] Remove a condition from check_complete Co-Authored-By: Nicholas Bollweg --- IPython/core/inputtransformer2.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 77b905c465d..5777559fbd4 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -644,7 +644,6 @@ def check_complete(self, cell: str): and tokens_by_line[-1][-1].type in newline_types: tokens_by_line.pop() - while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types: tokens_by_line[-1].pop() @@ -681,9 +680,6 @@ def check_complete(self, cell: str): return 'complete', None return 'incomplete', find_last_indent(lines) - if len(tokens_by_line[-1]) <= 1: - return 'incomplete', find_last_indent(lines) - # If there's a blank line at the end, assume we're ready to execute if not lines[-1].strip(): return 'complete', None From d3d01a4b038de61169c5528211b9c60a7ac98c97 Mon Sep 17 00:00:00 2001 From: kd2718 Date: Sat, 6 Oct 2018 13:32:59 -0700 Subject: [PATCH 0342/3726] removed type hints to allow for python 3.5 support --- IPython/core/magics/osm.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 2bf8938977d..5fe268da4ba 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -24,7 +24,6 @@ from IPython.utils.openpy import source_to_unicode from IPython.utils.process import abbrev_cwd from IPython.utils.terminal import set_term_title -from os import DirEntry @magics_class @@ -35,7 +34,7 @@ class OSMagics(Magics): def __init__(self, shell=None, **kwargs): # Now define isexec in a cross platform manner. - self.is_posix: bool = False + self.is_posix = False self.execre = None if os.name == 'posix': self.is_posix = True @@ -52,29 +51,29 @@ def __init__(self, shell=None, **kwargs): @skip_doctest - def _isexec_POSIX(self, f:DirEntry) -> bool: + def _isexec_POSIX(self, file): """ Test for executible on a POSIX system """ - return f.is_file() and os.access(f.path, os.X_OK) + return file.is_file() and os.access(file.path, os.X_OK) @skip_doctest - def _isexec_WIN(self, f:DirEntry) -> int: + def _isexec_WIN(self, file): """ Test for executible file on non POSIX system """ - return f.is_file() and self.execre.match(f.name) is not None + return file.is_file() and self.execre.match(file.name) is not None @skip_doctest - def isexec(self, f:DirEntry) -> bool: + def isexec(self, file): """ Test for executible file on non POSIX system """ if self.is_posix: - return self._isexec_POSIX(f) + return self._isexec_POSIX(file) else: - return self._isexec_WIN(f) + return self._isexec_WIN(file) @skip_doctest @@ -212,7 +211,7 @@ def rehashx(self, parameter_s=''): try: # write the whole loop for posix/Windows so we don't have an if in # the innermost part - if os.name == 'posix': + if self.is_posix: for pdir in path: try: os.chdir(pdir) From 6bc0e768a8571adc5edc5ab1ebfe9bd5925f564c Mon Sep 17 00:00:00 2001 From: kd2718 Date: Sat, 6 Oct 2018 14:14:39 -0700 Subject: [PATCH 0343/3726] issue is with 'with'statetment --- IPython/core/magics/osm.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 5fe268da4ba..6a31076778c 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -212,25 +212,27 @@ def rehashx(self, parameter_s=''): # write the whole loop for posix/Windows so we don't have an if in # the innermost part if self.is_posix: + print(path) for pdir in path: try: os.chdir(pdir) except OSError: continue - with os.scandir(pdir) as dirlist: - for ff in dirlist: - if self.isexec(ff): - fname = ff.name - try: - # Removes dots from the name since ipython - # will assume names with dots to be python. - if not self.shell.alias_manager.is_alias(fname): - self.shell.alias_manager.define_alias( - fname.replace('.',''), fname) - except InvalidAliasError: - pass - else: - syscmdlist.append(fname) + dirlist = os.scandir(path=pdir) + #with os.scandir(pdir) as dirlist: + for ff in dirlist: + if self.isexec(ff): + fname = ff.name + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + if not self.shell.alias_manager.is_alias(fname): + self.shell.alias_manager.define_alias( + fname.replace('.',''), fname) + except InvalidAliasError: + pass + else: + syscmdlist.append(fname) else: no_alias = Alias.blacklist for pdir in path: From 54f7c6e0f884877f021361490e847812aea44312 Mon Sep 17 00:00:00 2001 From: kd2718 Date: Sat, 6 Oct 2018 19:40:46 -0700 Subject: [PATCH 0344/3726] removed with notation. This is not supported by python 3.5 --- IPython/core/magics/osm.py | 35 +++++++++++++++++++---------------- IPython/core/profileapp.py | 20 ++++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 6a31076778c..d77cf8a24ad 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -218,8 +218,9 @@ def rehashx(self, parameter_s=''): os.chdir(pdir) except OSError: continue + + # use with notation for python 3.6 onward dirlist = os.scandir(path=pdir) - #with os.scandir(pdir) as dirlist: for ff in dirlist: if self.isexec(ff): fname = ff.name @@ -240,21 +241,23 @@ def rehashx(self, parameter_s=''): os.chdir(pdir) except OSError: continue - with os.scandir(pdir) as dirlist: - for ff in dirlist: - fname = ff.name - base, ext = os.path.splitext(fname) - if self.isexec(ff) and base.lower() not in no_alias: - if ext.lower() == '.exe': - fname = base - try: - # Removes dots from the name since ipython - # will assume names with dots to be python. - self.shell.alias_manager.define_alias( - base.lower().replace('.',''), fname) - except InvalidAliasError: - pass - syscmdlist.append(fname) + + # use with notation for python 3.6 onward + dirlist = os.scandir(pdir) + for ff in dirlist: + fname = ff.name + base, ext = os.path.splitext(fname) + if self.isexec(ff) and base.lower() not in no_alias: + if ext.lower() == '.exe': + fname = base + try: + # Removes dots from the name since ipython + # will assume names with dots to be python. + self.shell.alias_manager.define_alias( + base.lower().replace('.',''), fname) + except InvalidAliasError: + pass + syscmdlist.append(fname) self.shell.db['syscmdlist'] = syscmdlist finally: os.chdir(savedir) diff --git a/IPython/core/profileapp.py b/IPython/core/profileapp.py index 700fcc7a008..2f66bd99a8c 100644 --- a/IPython/core/profileapp.py +++ b/IPython/core/profileapp.py @@ -97,10 +97,12 @@ def list_profiles_in(path): """list profiles in a given root directory""" profiles = [] - with os.scandir(path) as files: - for f in files: - if f.is_dir() and f.name.startswith('profile_'): - profiles.append(f.name.split('_', 1)[-1]) + + # use with notation for python 3.6 onward + files = os.scandir(path) + for f in files: + if f.is_dir() and f.name.startswith('profile_'): + profiles.append(f.name.split('_', 1)[-1]) return profiles @@ -108,10 +110,12 @@ def list_bundled_profiles(): """list profiles that are bundled with IPython.""" path = os.path.join(get_ipython_package_dir(), u'core', u'profile') profiles = [] - with os.scandir(path) as files: - for profile in files: - if profile.is_dir() and profile.name != "__pycache__": - profiles.append(profile.name) + + # use with notation for python 3.6 onward + files = os.scandir(path) + for profile in files: + if profile.is_dir() and profile.name != "__pycache__": + profiles.append(profile.name) return profiles From 3cdff565a9a98cd089d68e34c81854180b11224a Mon Sep 17 00:00:00 2001 From: Michael Penkov Date: Sun, 7 Oct 2018 11:49:28 +0900 Subject: [PATCH 0345/3726] don't warn if the iframe isn't the only thing in the data --- IPython/core/display.py | 2 +- IPython/core/tests/test_display.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 4a2e2817910..76281b189fc 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -667,7 +667,7 @@ def _repr_pretty_(self, pp, cycle): class HTML(TextDisplayObject): def __init__(self, data=None, url=None, filename=None, metadata=None): - if data and ""): warnings.warn("Consider using IPython.display.IFrame instead") super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index 7c1f978b42c..24cdc030091 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -200,6 +200,9 @@ def test_encourage_iframe_over_html(m_warn): display.HTML('
') m_warn.assert_not_called() + display.HTML('

Lots of content here

') + m_warn.assert_not_called() + display.HTML('') m_warn.assert_called_with('Consider using IPython.display.IFrame instead') From 5e391e0c816ad043a8a5dd1151eb63f525385354 Mon Sep 17 00:00:00 2001 From: Michael Penkov Date: Sun, 7 Oct 2018 11:59:47 +0900 Subject: [PATCH 0346/3726] handle case-insensitivity --- IPython/core/display.py | 14 +++++++++++++- IPython/core/tests/test_display.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 76281b189fc..84c18e51c43 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -667,7 +667,19 @@ def _repr_pretty_(self, pp, cycle): class HTML(TextDisplayObject): def __init__(self, data=None, url=None, filename=None, metadata=None): - if data and data.startswith("") + + if warn(): warnings.warn("Consider using IPython.display.IFrame instead") super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index 24cdc030091..1fed51127a1 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -197,6 +197,9 @@ def test_displayobject_repr(): @mock.patch('warnings.warn') def test_encourage_iframe_over_html(m_warn): + display.HTML() + m_warn.assert_not_called() + display.HTML('
') m_warn.assert_not_called() @@ -206,6 +209,10 @@ def test_encourage_iframe_over_html(m_warn): display.HTML('') m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + m_warn.reset_mock() + display.HTML('') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + def test_progress(): p = display.ProgressBar(10) nt.assert_in('0/10',repr(p)) From bdaec6ffae6f8f4dfa644bffeab93c1e5ad30f99 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sat, 6 Oct 2018 23:41:04 -0400 Subject: [PATCH 0347/3726] Remove redundant test Nice catch @bartskowron --- IPython/core/tests/test_inputtransformer2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index 9c5a413955f..6a57b681c64 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -215,7 +215,6 @@ def test_check_complete(): nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None)) nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash nt.assert_equal(cc("1\\\n+2"), ('complete', None)) - nt.assert_equal(cc("1\\\n+2"), ('complete', None)) nt.assert_equal(cc("exit"), ('complete', None)) example = dedent(""" From 90eadf9f123185bfb15e021606fea349e14861e4 Mon Sep 17 00:00:00 2001 From: Shashank Kumar Date: Mon, 8 Oct 2018 15:51:40 +0530 Subject: [PATCH 0348/3726] docs(CONTRIBUTING): Minor fix --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60a0bd2c1be..576f1ae7800 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ To remove a label, use the `untag` command: > @meeseeksdev untag windows, documentation -e'll be adding additional capabilities for the bot and will share them here +We'll be adding additional capabilities for the bot and will share them here when they are ready to be used. ## Opening an Issue From 43e857d948594ec7e5a2c3f1ec865574b5fe4792 Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Mon, 8 Oct 2018 19:31:16 +0500 Subject: [PATCH 0349/3726] Replace depricated time.clock with time.perf_counter issue#11375 --- IPython/utils/timing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 3d4d9f8d9bc..6bb176f338c 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -59,12 +59,12 @@ def clock2(): except ImportError: # There is no distinction of user/system time under windows, so we just use # time.clock() for everything... - clocku = clocks = clock = time.clock + clocku = clocks = clock = time.perf_counter def clock2(): """Under windows, system CPU time can't be measured. This just returns clock() and zero.""" - return time.clock(),0.0 + return time.perf_counter(),0.0 def timings_out(reps,func,*args,**kw): From 1d0e132d78ee6c66a119c1d12aaa83eca4da326e Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Mon, 8 Oct 2018 22:23:52 +0500 Subject: [PATCH 0350/3726] Update comments issue#11375 --- IPython/utils/timing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 6bb176f338c..9a31affadf0 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -58,12 +58,12 @@ def clock2(): return resource.getrusage(resource.RUSAGE_SELF)[:2] except ImportError: # There is no distinction of user/system time under windows, so we just use - # time.clock() for everything... + # time.perff_counter() for everything... clocku = clocks = clock = time.perf_counter def clock2(): """Under windows, system CPU time can't be measured. - This just returns clock() and zero.""" + This just returns perf_counter() and zero.""" return time.perf_counter(),0.0 From 79f4369879e11a76d4694ad099b10295d8a11e3b Mon Sep 17 00:00:00 2001 From: kd2718 Date: Mon, 8 Oct 2018 21:05:09 -0700 Subject: [PATCH 0351/3726] quick doc change --- IPython/core/magics/execution.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 9fabc43d5a4..6f7ddea516a 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -188,7 +188,7 @@ def prun(self, parameter_s='', cell=None): """Run a statement through the python code profiler. - Usage, in line mode: + Usage, in line mode:run %prun [options] statement Usage, in cell mode: @@ -507,6 +507,8 @@ def run(self, parameter_s='', runner=None, *two* back slashes (e.g. ``\\\\*``) to suppress expansions. To completely disable these expansions, you can use -G flag. + On Windows systems, the use of double quotes `"` is required. + Options: -n From d02e73a61e94d24611cdf6bbf68736e860abed8a Mon Sep 17 00:00:00 2001 From: Christopher Moura Date: Tue, 9 Oct 2018 13:37:44 -0300 Subject: [PATCH 0352/3726] Update README.rst Fixed a minor typo. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index eb21641997f..0ce6166b6f4 100644 --- a/README.rst +++ b/README.rst @@ -95,7 +95,7 @@ As well as the following Pull-Request for discussion: This error does also occur if you are invoking ``setup.py`` directly – which you should not – or are using ``easy_install`` If this is the case, use ``pip -install .`` (instead of ``setup.py install`` , and ``pip install -e .`` instead +install .`` instead of ``setup.py install`` , and ``pip install -e .`` instead of ``setup.py develop`` If you are depending on IPython as a dependency you may also want to have a conditional dependency on IPython depending on the Python version:: From 9fe8eab4f00d6cd25e850a714d1690a629674f95 Mon Sep 17 00:00:00 2001 From: hongshaoyang Date: Wed, 10 Oct 2018 01:19:45 +0800 Subject: [PATCH 0353/3726] Update osm.py --- IPython/core/magics/osm.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index f835361a4cf..b3fda954f4b 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -776,10 +776,12 @@ def writefile(self, line, cell): The file will be overwritten unless the -a (--append) flag is specified. """ - line = line if len(line.split())==1 else '"%s"' % line.strip("\"\'") args = magic_arguments.parse_argstring(self.writefile, line) - filename = os.path.expanduser(args.filename.strip("\"\'")) - + if re.match(r'[\'*\']|["*"]', args.filename): + filename = os.path.expanduser(args.filename[1:-1]) + else: + filename = os.path.expanduser(args.filename) + if os.path.exists(filename): if args.append: print("Appending to %s" % filename) From 3d766d48950ab70b3266a51f827c14e1272f39fa Mon Sep 17 00:00:00 2001 From: felixzhuologist Date: Tue, 9 Oct 2018 17:13:34 -0400 Subject: [PATCH 0354/3726] check for nonlocal inside toplevel functions --- IPython/core/async_helpers.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index a6ff86031d0..d2d639c4f6b 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -97,19 +97,25 @@ class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): the implementation involves wrapping the repl in an async function, it is erroneously allowed (e.g. yield or return at the top level) """ + def __init__(self, is_toplevel=True): + self.is_toplevel = is_toplevel + super().__init__() def generic_visit(self, node): func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) + toplevel_invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) + inner_invalid_types = (ast.Nonlocal,) - if isinstance(node, func_types): - return # Don't recurse into functions - elif isinstance(node, invalid_types): + if isinstance(node, func_types) and self.is_toplevel: + self.is_toplevel = False + super().generic_visit(node) + elif self.is_toplevel and isinstance(node, toplevel_invalid_types): + raise SyntaxError() + elif not self.is_toplevel and isinstance(node, inner_invalid_types): raise SyntaxError() else: super().generic_visit(node) - def _async_parse_cell(cell: str) -> ast.AST: """ This is a compatibility shim for pre-3.7 when async outside of a function From 8e7c5f093ba469e6dffdff4bd9bf63abeac1ba60 Mon Sep 17 00:00:00 2001 From: felixzhuologist Date: Tue, 9 Oct 2018 17:16:53 -0400 Subject: [PATCH 0355/3726] generalize to arbitrary errors by depth --- IPython/core/async_helpers.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index d2d639c4f6b..c9ba18225d5 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -97,25 +97,27 @@ class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): the implementation involves wrapping the repl in an async function, it is erroneously allowed (e.g. yield or return at the top level) """ - def __init__(self, is_toplevel=True): - self.is_toplevel = is_toplevel + def __init__(self): + self.depth = 0 super().__init__() def generic_visit(self, node): func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - toplevel_invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) - inner_invalid_types = (ast.Nonlocal,) - - if isinstance(node, func_types) and self.is_toplevel: - self.is_toplevel = False + invalid_types_by_depth = { + 0: (ast.Return, ast.Yield, ast.YieldFrom), + 1: (ast.Nonlocal,) + } + + should_traverse = self.depth < max(invalid_types_by_depth.keys()) + if isinstance(node, func_types) and should_traverse: + self.depth += 1 super().generic_visit(node) - elif self.is_toplevel and isinstance(node, toplevel_invalid_types): - raise SyntaxError() - elif not self.is_toplevel and isinstance(node, inner_invalid_types): + elif isinstance(node, invalid_types_by_depth[self.depth]): raise SyntaxError() else: super().generic_visit(node) + def _async_parse_cell(cell: str) -> ast.AST: """ This is a compatibility shim for pre-3.7 when async outside of a function From a562329af4c842d00d3e93767c4576f91849de15 Mon Sep 17 00:00:00 2001 From: felixzhuologist Date: Tue, 9 Oct 2018 18:39:09 -0400 Subject: [PATCH 0356/3726] add test for nonlocal case --- IPython/core/tests/test_async_helpers.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 6bf64e2115f..20ad3d0fc5b 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -227,6 +227,35 @@ def nest_case(context, case): else: iprc(cell) + def test_nonlocal(self): + # fails if outer scope is not a function scope or if var not defined + with self.assertRaises(SyntaxError): + iprc("nonlocal x") + iprc(""" + x = 1 + def f(): + nonlocal x + x = 10000 + yield x + """) + iprc(""" + def f(): + def g(): + nonlocal x + x = 10000 + yield x + """) + + # works if outer scope is a function scope and var exists + iprc(""" + def f(): + x = 20 + def g(): + nonlocal x + x = 10000 + yield x + """) + def test_execute(self): iprc(""" From d5a746e1c0515220c3cc6672c4feaa9c5f7170a2 Mon Sep 17 00:00:00 2001 From: Kory Donati Date: Wed, 10 Oct 2018 13:40:12 -0700 Subject: [PATCH 0357/3726] removed args initializer that is not used and removed unneeded code in osm init super call --- IPython/core/magics/osm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index d77cf8a24ad..ab3c4e3320e 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -47,7 +47,7 @@ def __init__(self, shell=None, **kwargs): self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) # call up the chain - super(OSMagics, self).__init__(shell=shell, **kwargs) + super().__init__(shell=shell, **kwargs) @skip_doctest @@ -520,7 +520,6 @@ def dhist(self, parameter_s=''): dh = self.shell.user_ns['_dh'] if parameter_s: - args = [] try: args = map(int,parameter_s.split()) except: From 1b0793cf32e8a5f57ca49c6991861c66d5f8e6db Mon Sep 17 00:00:00 2001 From: koryd Date: Wed, 10 Oct 2018 14:27:40 -0700 Subject: [PATCH 0358/3726] better update for the docs --- IPython/core/magics/execution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 6f7ddea516a..74a9e6a264f 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -507,7 +507,8 @@ def run(self, parameter_s='', runner=None, *two* back slashes (e.g. ``\\\\*``) to suppress expansions. To completely disable these expansions, you can use -G flag. - On Windows systems, the use of double quotes `"` is required. + On Windows systems, the use of single quotes `'` when specifing + a file is not supported. Use double quotes `"`. Options: From d2a9a9d7254ea6a0402134c293ed99251e448f81 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 11 Oct 2018 12:57:34 +0200 Subject: [PATCH 0359/3726] Same highlighting for %%file as for %%writefile ... because former is an alias for latter. --- IPython/lib/lexers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/lib/lexers.py b/IPython/lib/lexers.py index b8dec7e43b9..fd42880a9fd 100644 --- a/IPython/lib/lexers.py +++ b/IPython/lib/lexers.py @@ -98,6 +98,7 @@ def build_ipy_lexer(python3): (r'(?s)(\s*)(%%time)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), (r'(?s)(\s*)(%%timeit)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), (r'(?s)(\s*)(%%writefile)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%file)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), (r'(?s)(^\s*)(%%!)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(BashLexer))), (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), From a2a029f058577ae807b674154c370ee84b51a858 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 11 Oct 2018 12:58:31 +0200 Subject: [PATCH 0360/3726] Fix %%perl highlighting --- IPython/lib/lexers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/lib/lexers.py b/IPython/lib/lexers.py index fd42880a9fd..a8c871098a0 100644 --- a/IPython/lib/lexers.py +++ b/IPython/lib/lexers.py @@ -88,7 +88,7 @@ def build_ipy_lexer(python3): (r'(?s)(\s*)(%%javascript)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), (r'(?s)(\s*)(%%js)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), (r'(?s)(\s*)(%%latex)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(TexLexer))), - (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), + (r'(?s)(\s*)(%%perl)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), (r'(?s)(\s*)(%%prun)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), (r'(?s)(\s*)(%%python)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), From 1c7897a268b3e5ea80df669774d84835c92fd666 Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Thu, 11 Oct 2018 18:29:18 +0500 Subject: [PATCH 0361/3726] Replace deprecated time.time() with time.perf_counter() issue#11375 --- IPython/core/magics/execution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 9fabc43d5a4..cb1cb2b3cb3 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -912,7 +912,7 @@ def _run_with_timing(run, nruns): Number of times to execute `run`. """ - twall0 = time.time() + twall0 = time.perf_counter() if nruns == 1: t0 = clock2() run() @@ -935,7 +935,7 @@ def _run_with_timing(run, nruns): print(" Times : %10s %10s" % ('Total', 'Per run')) print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) - twall1 = time.time() + twall1 = time.perf_counter() print("Wall time: %10.2f s." % (twall1 - twall0)) @skip_doctest From 977474a07f211739ad5d1724967db2dafe052299 Mon Sep 17 00:00:00 2001 From: wim glenn Date: Thu, 11 Oct 2018 11:00:24 -0500 Subject: [PATCH 0362/3726] Fix a paragraph that read very poorly --- docs/source/whatsnew/version7.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 1a8e1eff67c..19352cc0d95 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -118,11 +118,11 @@ Jupyter Protocol will need further updates of the IPykernel package. Non-Asynchronous code ~~~~~~~~~~~~~~~~~~~~~ -As the internal API of IPython are now asynchronous, IPython need to run under -an even loop. In order to allow many workflow, (like using the :magic:`%run` +As the internal API of IPython is now asynchronous, IPython needs to run under +an event loop. In order to allow many workflows, (like using the :magic:`%run` magic, or copy_pasting code that explicitly starts/stop event loop), when top-level code is detected as not being asynchronous, IPython code is advanced -via a pseudo-synchronous runner, and will not may not advance pending tasks. +via a pseudo-synchronous runner, and may not advance pending tasks. Change to Nested Embed ~~~~~~~~~~~~~~~~~~~~~~ From 391ef36d08f8492c0df5a8153de59a51d8cc5e19 Mon Sep 17 00:00:00 2001 From: Shao Yang Date: Fri, 12 Oct 2018 22:55:00 +0800 Subject: [PATCH 0363/3726] add test to core/tests/test_magic --- IPython/core/tests/test_magic.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index dfeabca1f21..7d5fa13a020 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -27,7 +27,8 @@ from IPython.testing import decorators as dec from IPython.testing import tools as tt from IPython.utils.io import capture_output -from IPython.utils.tempdir import TemporaryDirectory +from IPython.utils.tempdir import (TemporaryDirectory, + TemporaryWorkingDirectory) from IPython.utils.process import find_cmd @@ -797,7 +798,20 @@ def test_file_amend(): s = f.read() nt.assert_in('line1\n', s) nt.assert_in('line3\n', s) - + +def test_file_spaces(): + """%%file with spaces in filename""" + ip = get_ipython() + with TemporaryWorkingDirectory() as td: + fname = 'file name' + ip.run_cell_magic("file", "'%s'"%fname, u'\n'.join([ + 'line1', + 'line2', + ])) + with open(fname) as f: + s = f.read() + nt.assert_in('line1\n', s) + nt.assert_in('line2', s) def test_script_config(): ip = get_ipython() From 50c9ae6b40c2c0ebaa86d087a90982eec61a1352 Mon Sep 17 00:00:00 2001 From: hongshaoyang Date: Fri, 12 Oct 2018 23:05:13 +0800 Subject: [PATCH 0364/3726] Update test_magic.py --- IPython/core/tests/test_magic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 7d5fa13a020..74d2604fa67 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -803,7 +803,7 @@ def test_file_spaces(): """%%file with spaces in filename""" ip = get_ipython() with TemporaryWorkingDirectory() as td: - fname = 'file name' + fname = "file name" ip.run_cell_magic("file", "'%s'"%fname, u'\n'.join([ 'line1', 'line2', From 0ca8ce4b1a4e380fe4afcb48574901c36c055119 Mon Sep 17 00:00:00 2001 From: Shao Yang Date: Fri, 12 Oct 2018 23:19:38 +0800 Subject: [PATCH 0365/3726] change magics from %%file to %%writefile --- IPython/core/tests/test_magic.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index dfeabca1f21..b9c1533905b 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -738,11 +738,11 @@ def cellm33(self, line, cell): nt.assert_equal(c33, None) def test_file(): - """Basic %%file""" + """Basic %%writefile""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ 'line1', 'line2', ])) @@ -752,12 +752,12 @@ def test_file(): nt.assert_in('line2', s) def test_file_var_expand(): - """%%file $filename""" + """%%writefile $filename""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') ip.user_ns['filename'] = fname - ip.run_cell_magic("file", '$filename', u'\n'.join([ + ip.run_cell_magic("writefile", '$filename', u'\n'.join([ 'line1', 'line2', ])) @@ -767,11 +767,11 @@ def test_file_var_expand(): nt.assert_in('line2', s) def test_file_unicode(): - """%%file with unicode cell""" + """%%writefile with unicode cell""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ u'liné1', u'liné2', ])) @@ -781,15 +781,15 @@ def test_file_unicode(): nt.assert_in(u'liné2', s) def test_file_amend(): - """%%file -a amends files""" + """%%writefile -a amends files""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file2') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ 'line1', 'line2', ])) - ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ + ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([ 'line3', 'line4', ])) From a5731aa33cd94e2441215eae55d1db1516b2474d Mon Sep 17 00:00:00 2001 From: Shao Yang Date: Fri, 12 Oct 2018 23:42:18 +0800 Subject: [PATCH 0366/3726] wrong quotes --- IPython/core/tests/test_magic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 74d2604fa67..f975076ca1f 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -804,7 +804,7 @@ def test_file_spaces(): ip = get_ipython() with TemporaryWorkingDirectory() as td: fname = "file name" - ip.run_cell_magic("file", "'%s'"%fname, u'\n'.join([ + ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([ 'line1', 'line2', ])) From b73398c4a228f8e8cb6e55f0f18956d1df766590 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Oct 2018 09:35:39 -0700 Subject: [PATCH 0367/3726] Use ansi code for soon-to-be released prompt_toolkit. Prompt toolkit use to try to map 256 colors code to closest ansi code but does not do that anymore. This should fix (some of) the occurrences. --- IPython/terminal/interactiveshell.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 35cb0697849..fed68c67864 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -297,9 +297,9 @@ def _make_style_from_name_or_cls(self, name_or_cls): Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', Token.Prompt: '#009900', - Token.PromptNum: '#00ff00 bold', + Token.PromptNum: '#ansibrightgreen bold', Token.OutPrompt: '#990000', - Token.OutPromptNum: '#ff0000 bold', + Token.OutPromptNum: '#ansibrightred bold', }) # Hack: Due to limited color support on the Windows console @@ -323,9 +323,9 @@ def _make_style_from_name_or_cls(self, name_or_cls): style_cls = name_or_cls style_overrides = { Token.Prompt: '#009900', - Token.PromptNum: '#00ff00 bold', + Token.PromptNum: '#ansibrightgreen bold', Token.OutPrompt: '#990000', - Token.OutPromptNum: '#ff0000 bold', + Token.OutPromptNum: '#ansibrightred bold', } style_overrides.update(self.highlighting_style_overrides) style = merge_styles([ From 5cc53e88f34d82c96dca26cf708d1d78f19cea82 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Oct 2018 15:58:09 -0700 Subject: [PATCH 0368/3726] Add test that %depug pass through generators. --- IPython/terminal/tests/test_debug_magic.py | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 IPython/terminal/tests/test_debug_magic.py diff --git a/IPython/terminal/tests/test_debug_magic.py b/IPython/terminal/tests/test_debug_magic.py new file mode 100644 index 00000000000..650ba7f9ab9 --- /dev/null +++ b/IPython/terminal/tests/test_debug_magic.py @@ -0,0 +1,74 @@ +"""Test embedding of IPython""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2013 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import sys +from IPython.testing.decorators import skip_win32 + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +@skip_win32 +def test_debug_magic_passes_through_generators(): + """ + This test that we can correctly pass through frames of a generator post-mortem. + """ + import pexpect + import re + in_prompt = re.compile(b'In ?\[\\d+\]:') + ipdb_prompt = 'ipdb>' + env = os.environ.copy() + child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor', '--simple-prompt'], + env=env) + child.timeout = 2 + + child.expect(in_prompt) + child.sendline("def f(x):") + child.sendline(" raise Exception") + child.sendline("") + + child.expect(in_prompt) + child.sendline("gen = (f(x) for x in [0])") + child.sendline("") + + child.expect(in_prompt) + child.sendline("for x in gen:") + child.sendline(" pass") + child.sendline("") + + child.expect('Exception:') + + child.expect(in_prompt) + child.sendline(r'%debug') + child.expect('----> 2 raise Exception') + + child.expect(ipdb_prompt) + child.sendline('u') + child.expect_exact(r'----> 1 gen = (f(x) for x in [0])') + + child.expect(ipdb_prompt) + child.sendline('u') + child.expect_exact('----> 1 for x in gen:') + + child.expect(ipdb_prompt) + child.sendline('u') + child.expect_exact('*** Oldest frame') + + child.expect(ipdb_prompt) + child.sendline('exit') + + child.expect(in_prompt) + child.sendline('exit') + + child.close() From 5c6aa5ea5ad94ae0f40d8e04faa348a7003ef8cb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Oct 2018 17:03:06 -0700 Subject: [PATCH 0369/3726] Give some love to the VI mode. Improve #11329, still likely need a magic to make editign mode easier to toggle --- IPython/terminal/interactiveshell.py | 19 ++++++++++++++++++- IPython/terminal/prompts.py | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 35cb0697849..e26008a02ee 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -12,7 +12,7 @@ from IPython.utils.process import abbrev_cwd from traitlets import ( Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union, - Any, + Any, validate ) from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode @@ -131,6 +131,23 @@ def debugger_cls(self): highlighting. To see available styles, run `pygmentize -L styles`.""" ).tag(config=True) + @validate('editing_mode') + def _validate_editing_mode(self, proposal): + if proposal['value'].lower() == 'vim': + proposal['value']= 'vi' + elif proposal['value'].lower() == 'default': + proposal['value']= 'emacs' + + if hasattr(EditingMode, proposal['value'].upper()): + return proposal['value'].lower() + + return self.editing_mode + + + @observe('editing_mode') + def _editing_mode(self, change): + u_mode = change.new.upper() + self.pt_app.editing_mode = u_mode @observe('highlighting_style') @observe('colors') diff --git a/IPython/terminal/prompts.py b/IPython/terminal/prompts.py index ce8c169f408..6b7b7cc9dc3 100644 --- a/IPython/terminal/prompts.py +++ b/IPython/terminal/prompts.py @@ -13,8 +13,15 @@ class Prompts(object): def __init__(self, shell): self.shell = shell + def vi_mode(self): + if self.shell.pt_app.editing_mode == 'VI': + return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] ' + return '' + + def in_prompt_tokens(self): return [ + (Token.Prompt, self.vi_mode() ), (Token.Prompt, 'In ['), (Token.PromptNum, str(self.shell.execution_count)), (Token.Prompt, ']: '), From cb9cbcae51ea247525796a18fa7ae7cd5a6af2c0 Mon Sep 17 00:00:00 2001 From: kd2718 Date: Fri, 12 Oct 2018 18:09:39 -0700 Subject: [PATCH 0370/3726] updated comments. removed debug print statement --- IPython/core/magics/osm.py | 6 +++--- IPython/core/profileapp.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index d77cf8a24ad..c20b8397660 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -212,14 +212,13 @@ def rehashx(self, parameter_s=''): # write the whole loop for posix/Windows so we don't have an if in # the innermost part if self.is_posix: - print(path) for pdir in path: try: os.chdir(pdir) except OSError: continue - # use with notation for python 3.6 onward + # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: dirlist = os.scandir(path=pdir) for ff in dirlist: if self.isexec(ff): @@ -242,7 +241,7 @@ def rehashx(self, parameter_s=''): except OSError: continue - # use with notation for python 3.6 onward + # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: dirlist = os.scandir(pdir) for ff in dirlist: fname = ff.name @@ -258,6 +257,7 @@ def rehashx(self, parameter_s=''): except InvalidAliasError: pass syscmdlist.append(fname) + self.shell.db['syscmdlist'] = syscmdlist finally: os.chdir(savedir) diff --git a/IPython/core/profileapp.py b/IPython/core/profileapp.py index 2f66bd99a8c..97434e3d0b5 100644 --- a/IPython/core/profileapp.py +++ b/IPython/core/profileapp.py @@ -98,7 +98,7 @@ def list_profiles_in(path): """list profiles in a given root directory""" profiles = [] - # use with notation for python 3.6 onward + # for python 3.6+ rewrite to: with os.scandir(path) as dirlist: files = os.scandir(path) for f in files: if f.is_dir() and f.name.startswith('profile_'): @@ -111,7 +111,7 @@ def list_bundled_profiles(): path = os.path.join(get_ipython_package_dir(), u'core', u'profile') profiles = [] - # use with notation for python 3.6 onward + # for python 3.6+ rewrite to: with os.scandir(path) as dirlist: files = os.scandir(path) for profile in files: if profile.is_dir() and profile.name != "__pycache__": From dc0ceb16f92e0e943435106e863a5cfccd724a2d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 13 Oct 2018 17:14:22 +0300 Subject: [PATCH 0371/3726] Replace simplegeneric.generic with functools.singledispatch --- IPython/core/tests/test_completer.py | 2 +- IPython/external/__init__.py | 2 +- IPython/utils/generics.py | 10 +++------- IPython/utils/text.py | 4 ++-- setup.py | 1 - 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 56428bad2c8..74c53ade984 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -115,7 +115,7 @@ def test_custom_completion_error(): class A(object): pass ip.user_ns['a'] = A() - @complete_object.when_type(A) + @complete_object.register(A) def complete_A(a, existing_completions): raise TypeError("this should be silenced") diff --git a/IPython/external/__init__.py b/IPython/external/__init__.py index 3104c194622..1c8c546f118 100644 --- a/IPython/external/__init__.py +++ b/IPython/external/__init__.py @@ -2,4 +2,4 @@ This package contains all third-party modules bundled with IPython. """ -__all__ = ["simplegeneric"] +__all__ = [] diff --git a/IPython/utils/generics.py b/IPython/utils/generics.py index 5ffdc86ebda..fcada6f44df 100644 --- a/IPython/utils/generics.py +++ b/IPython/utils/generics.py @@ -1,20 +1,18 @@ # encoding: utf-8 """Generic functions for extending IPython. - -See http://pypi.python.org/pypi/simplegeneric. """ from IPython.core.error import TryNext -from simplegeneric import generic +from functools import singledispatch -@generic +@singledispatch def inspect_object(obj): """Called when you do obj?""" raise TryNext -@generic +@singledispatch def complete_object(obj, prev_completions): """Custom completer dispatching for python objects. @@ -30,5 +28,3 @@ def complete_object(obj, prev_completions): own_attrs + prev_completions. """ raise TryNext - - diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 0c0d82f6323..e844203ca0e 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -81,7 +81,7 @@ def get_paths(self): # print arg # # -# print_lsstring = result_display.when_type(LSString)(print_lsstring) +# print_lsstring = result_display.register(LSString)(print_lsstring) class SList(list): @@ -243,7 +243,7 @@ def sort(self,field= None, nums = False): # # nlprint(arg) # This was a nested list printer, now removed. # -# print_slist = result_display.when_type(SList)(print_slist) +# print_slist = result_display.register(SList)(print_slist) def indent(instr,nspaces=4, ntabs=0, flatten=False): diff --git a/setup.py b/setup.py index 78529cf6dbc..47c9af24307 100755 --- a/setup.py +++ b/setup.py @@ -188,7 +188,6 @@ 'jedi>=0.10', 'decorator', 'pickleshare', - 'simplegeneric>0.8', 'traitlets>=4.2', 'prompt_toolkit>=2.0.0,<2.1.0', 'pygments', From fde1eec1668b8d3b2d1e9b6b1fc2b7c6175f0885 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Oct 2018 17:11:53 -0700 Subject: [PATCH 0372/3726] support simple prompt --- IPython/terminal/prompts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/terminal/prompts.py b/IPython/terminal/prompts.py index 6b7b7cc9dc3..a108ca754c9 100644 --- a/IPython/terminal/prompts.py +++ b/IPython/terminal/prompts.py @@ -14,6 +14,8 @@ def __init__(self, shell): self.shell = shell def vi_mode(self): + if not hasattr(self.shell.pt_app, 'editing_mode'): + return '' if self.shell.pt_app.editing_mode == 'VI': return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] ' return '' From de447e5100faea067062cb87792ca16b7404c418 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 13 Oct 2018 08:59:52 -0700 Subject: [PATCH 0373/3726] typo --- IPython/core/magics/execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 74a9e6a264f..d04ace80c10 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -188,7 +188,7 @@ def prun(self, parameter_s='', cell=None): """Run a statement through the python code profiler. - Usage, in line mode:run + Usage, in line mode: %prun [options] statement Usage, in cell mode: From 8c11a03eaee54010dc0190c61dd9302d99f4bb4d Mon Sep 17 00:00:00 2001 From: luciana Date: Sun, 14 Oct 2018 12:49:53 -0300 Subject: [PATCH 0374/3726] added filter warnings for older versions of python (<3.7) This is a "squash" of three commits into one. This means that the intermediate history has been rewritten. --- IPython/core/interactiveshell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index c88423db238..de4a262c347 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -803,7 +803,9 @@ def init_deprecation_warnings(self): This will allow deprecation warning of function used interactively to show warning to users, and still hide deprecation warning from libraries import. """ - warnings.filterwarnings("default", category=DeprecationWarning, module=self.user_ns.get("__name__")) + if sys.version_info < (3,7): + warnings.filterwarnings("default", category=DeprecationWarning, module=self.user_ns.get("__name__")) + def init_builtins(self): # A single, static flag that we set to True. Its presence indicates From ba8538e5afb3c46e742f3a31e6046f936777d1a6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 14 Oct 2018 18:23:42 -0700 Subject: [PATCH 0375/3726] Fix to allow entering docstring into IPython. The EscapeTransformer find method was assuming incorrectly that every line would end with either a NEWLINE or EOF, while this is not the case when encountering multiple line string. This fixes that by making sure we don't index outside of bounds. With this IPython will correctly add a newline at the CLI. Closes #11391 --- IPython/core/inputtransformer2.py | 7 ++++++- IPython/core/tests/test_inputtransformer2.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 5777559fbd4..e2dd2d08773 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -355,9 +355,14 @@ def find(cls, tokens_by_line): """Find the first escaped command (%foo, !foo, etc.) in the cell. """ for line in tokens_by_line: + if not line: + continue ix = 0 - while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: + ll = len(line) + while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: ix += 1 + if ix >= ll: + continue if line[ix].string in ESCAPE_SINGLES: return cls(line[ix].start) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index 6a57b681c64..d6c2fa3bd6b 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -233,6 +233,17 @@ def test_check_complete(): for k in short: cc(c+k) +def test_check_complete_II(): + """ + Test that multiple line strings are properly handled. + + Separate test function for convenience + + """ + cc = ipt2.TransformerManager().check_complete + nt.assert_equal(cc('''def foo():\n """'''), ('incomplete', 4)) + + def test_null_cleanup_transformer(): manager = ipt2.TransformerManager() manager.cleanup_transforms.insert(0, null_cleanup_transformer) From e5ec6eb286615660dee78a6a8cf0b1268649f880 Mon Sep 17 00:00:00 2001 From: Massimo Santini Date: Mon, 15 Oct 2018 23:15:39 +0200 Subject: [PATCH 0376/3726] Attempt to fix latex symbols in the completer --- IPython/core/latex_symbols.py | 2143 +++++++++++++++++---------------- tools/gen_latex_symbols.py | 42 +- 2 files changed, 1095 insertions(+), 1090 deletions(-) diff --git a/IPython/core/latex_symbols.py b/IPython/core/latex_symbols.py index ca7200bb59f..164d917beb6 100644 --- a/IPython/core/latex_symbols.py +++ b/IPython/core/latex_symbols.py @@ -11,6 +11,7 @@ latex_symbols = { + "\\euler" : "ℯ", "\\^a" : "ᵃ", "\\^b" : "ᵇ", "\\^c" : "ᶜ", @@ -90,9 +91,9 @@ "\\_chi" : "ᵪ", "\\hbar" : "ħ", "\\sout" : "̶", - "\\textordfeminine" : "ª", + "\\ordfeminine" : "ª", "\\cdotp" : "·", - "\\textordmasculine" : "º", + "\\ordmasculine" : "º", "\\AA" : "Å", "\\AE" : "Æ", "\\DH" : "Ð", @@ -102,68 +103,68 @@ "\\aa" : "å", "\\ae" : "æ", "\\eth" : "ð", + "\\dh" : "ð", "\\o" : "ø", "\\th" : "þ", "\\DJ" : "Đ", "\\dj" : "đ", - "\\Elzxh" : "ħ", "\\imath" : "ı", + "\\jmath" : "ȷ", "\\L" : "Ł", "\\l" : "ł", "\\NG" : "Ŋ", "\\ng" : "ŋ", "\\OE" : "Œ", "\\oe" : "œ", - "\\texthvlig" : "ƕ", - "\\textnrleg" : "ƞ", - "\\textdoublepipe" : "ǂ", - "\\Elztrna" : "ɐ", - "\\Elztrnsa" : "ɒ", - "\\Elzopeno" : "ɔ", - "\\Elzrtld" : "ɖ", - "\\Elzschwa" : "ə", - "\\varepsilon" : "ɛ", - "\\Elzpgamma" : "ɣ", - "\\Elzpbgam" : "ɤ", - "\\Elztrnh" : "ɥ", - "\\Elzbtdl" : "ɬ", - "\\Elzrtll" : "ɭ", - "\\Elztrnm" : "ɯ", - "\\Elztrnmlr" : "ɰ", - "\\Elzltlmr" : "ɱ", - "\\Elzltln" : "ɲ", - "\\Elzrtln" : "ɳ", - "\\Elzclomeg" : "ɷ", - "\\textphi" : "ɸ", - "\\Elztrnr" : "ɹ", - "\\Elztrnrl" : "ɺ", - "\\Elzrttrnr" : "ɻ", - "\\Elzrl" : "ɼ", - "\\Elzrtlr" : "ɽ", - "\\Elzfhr" : "ɾ", - "\\Elzrtls" : "ʂ", - "\\Elzesh" : "ʃ", - "\\Elztrnt" : "ʇ", - "\\Elzrtlt" : "ʈ", - "\\Elzpupsil" : "ʊ", - "\\Elzpscrv" : "ʋ", - "\\Elzinvv" : "ʌ", - "\\Elzinvw" : "ʍ", - "\\Elztrny" : "ʎ", - "\\Elzrtlz" : "ʐ", - "\\Elzyogh" : "ʒ", - "\\Elzglst" : "ʔ", - "\\Elzreglst" : "ʕ", - "\\Elzinglst" : "ʖ", - "\\textturnk" : "ʞ", - "\\Elzdyogh" : "ʤ", - "\\Elztesh" : "ʧ", + "\\hvlig" : "ƕ", + "\\nrleg" : "ƞ", + "\\doublepipe" : "ǂ", + "\\trna" : "ɐ", + "\\trnsa" : "ɒ", + "\\openo" : "ɔ", + "\\rtld" : "ɖ", + "\\schwa" : "ə", + "\\varepsilon" : "ε", + "\\pgamma" : "ɣ", + "\\pbgam" : "ɤ", + "\\trnh" : "ɥ", + "\\btdl" : "ɬ", + "\\rtll" : "ɭ", + "\\trnm" : "ɯ", + "\\trnmlr" : "ɰ", + "\\ltlmr" : "ɱ", + "\\ltln" : "ɲ", + "\\rtln" : "ɳ", + "\\clomeg" : "ɷ", + "\\ltphi" : "ɸ", + "\\trnr" : "ɹ", + "\\trnrl" : "ɺ", + "\\rttrnr" : "ɻ", + "\\rl" : "ɼ", + "\\rtlr" : "ɽ", + "\\fhr" : "ɾ", + "\\rtls" : "ʂ", + "\\esh" : "ʃ", + "\\trnt" : "ʇ", + "\\rtlt" : "ʈ", + "\\pupsil" : "ʊ", + "\\pscrv" : "ʋ", + "\\invv" : "ʌ", + "\\invw" : "ʍ", + "\\trny" : "ʎ", + "\\rtlz" : "ʐ", + "\\yogh" : "ʒ", + "\\glst" : "ʔ", + "\\reglst" : "ʕ", + "\\inglst" : "ʖ", + "\\turnk" : "ʞ", + "\\dyogh" : "ʤ", + "\\tesh" : "ʧ", "\\rasp" : "ʼ", - "\\textasciicaron" : "ˇ", - "\\Elzverts" : "ˈ", - "\\Elzverti" : "ˌ", - "\\Elzlmrk" : "ː", - "\\Elzhlmrk" : "ˑ", + "\\verts" : "ˈ", + "\\verti" : "ˌ", + "\\lmrk" : "ː", + "\\hlmrk" : "ˑ", "\\grave" : "̀", "\\acute" : "́", "\\hat" : "̂", @@ -175,13 +176,12 @@ "\\ocirc" : "̊", "\\H" : "̋", "\\check" : "̌", - "\\Elzpalh" : "̡", - "\\Elzrh" : "̢", + "\\palh" : "̡", + "\\rh" : "̢", "\\c" : "̧", "\\k" : "̨", - "\\Elzsbbrg" : "̪", - "\\Elzxl" : "̵", - "\\Elzbar" : "̶", + "\\sbbrg" : "̪", + "\\strike" : "̶", "\\Alpha" : "Α", "\\Beta" : "Β", "\\Gamma" : "Γ", @@ -236,7 +236,7 @@ "\\Sampi" : "Ϡ", "\\varkappa" : "ϰ", "\\varrho" : "ϱ", - "\\textTheta" : "ϴ", + "\\varTheta" : "ϴ", "\\epsilon" : "ϵ", "\\dddot" : "⃛", "\\ddddot" : "⃜", @@ -249,7 +249,7 @@ "\\beth" : "ℶ", "\\gimel" : "ℷ", "\\daleth" : "ℸ", - "\\BbbPi" : "ℿ", + "\\bbPi" : "ℿ", "\\Zbar" : "Ƶ", "\\overbar" : "̅", "\\ovhook" : "̉", @@ -258,7 +258,6 @@ "\\ocommatopright" : "̕", "\\droang" : "̚", "\\wideutilde" : "̰", - "\\underbar" : "̱", "\\not" : "̸", "\\upMu" : "Μ", "\\upNu" : "Ν", @@ -281,1019 +280,1021 @@ "\\annuity" : "⃧", "\\threeunderdot" : "⃨", "\\widebridgeabove" : "⃩", - "\\BbbC" : "ℂ", - "\\Eulerconst" : "ℇ", - "\\mscrg" : "ℊ", - "\\mscrH" : "ℋ", - "\\mfrakH" : "ℌ", - "\\BbbH" : "ℍ", - "\\Planckconst" : "ℎ", - "\\mscrI" : "ℐ", - "\\mscrL" : "ℒ", - "\\BbbN" : "ℕ", - "\\BbbP" : "ℙ", - "\\BbbQ" : "ℚ", - "\\mscrR" : "ℛ", - "\\BbbR" : "ℝ", - "\\BbbZ" : "ℤ", - "\\mfrakZ" : "ℨ", + "\\bbC" : "ℂ", + "\\eulermascheroni" : "ℇ", + "\\scrg" : "ℊ", + "\\scrH" : "ℋ", + "\\frakH" : "ℌ", + "\\bbH" : "ℍ", + "\\planck" : "ℎ", + "\\scrI" : "ℐ", + "\\scrL" : "ℒ", + "\\bbN" : "ℕ", + "\\bbP" : "ℙ", + "\\bbQ" : "ℚ", + "\\scrR" : "ℛ", + "\\bbR" : "ℝ", + "\\bbZ" : "ℤ", + "\\frakZ" : "ℨ", "\\Angstrom" : "Å", - "\\mscrB" : "ℬ", - "\\mfrakC" : "ℭ", - "\\mscre" : "ℯ", - "\\mscrE" : "ℰ", - "\\mscrF" : "ℱ", + "\\scrB" : "ℬ", + "\\frakC" : "ℭ", + "\\scre" : "ℯ", + "\\scrE" : "ℰ", + "\\scrF" : "ℱ", "\\Finv" : "Ⅎ", - "\\mscrM" : "ℳ", - "\\mscro" : "ℴ", - "\\Bbbgamma" : "ℽ", - "\\BbbGamma" : "ℾ", - "\\mitBbbD" : "ⅅ", - "\\mitBbbd" : "ⅆ", - "\\mitBbbe" : "ⅇ", - "\\mitBbbi" : "ⅈ", - "\\mitBbbj" : "ⅉ", - "\\mbfA" : "𝐀", - "\\mbfB" : "𝐁", - "\\mbfC" : "𝐂", - "\\mbfD" : "𝐃", - "\\mbfE" : "𝐄", - "\\mbfF" : "𝐅", - "\\mbfG" : "𝐆", - "\\mbfH" : "𝐇", - "\\mbfI" : "𝐈", - "\\mbfJ" : "𝐉", - "\\mbfK" : "𝐊", - "\\mbfL" : "𝐋", - "\\mbfM" : "𝐌", - "\\mbfN" : "𝐍", - "\\mbfO" : "𝐎", - "\\mbfP" : "𝐏", - "\\mbfQ" : "𝐐", - "\\mbfR" : "𝐑", - "\\mbfS" : "𝐒", - "\\mbfT" : "𝐓", - "\\mbfU" : "𝐔", - "\\mbfV" : "𝐕", - "\\mbfW" : "𝐖", - "\\mbfX" : "𝐗", - "\\mbfY" : "𝐘", - "\\mbfZ" : "𝐙", - "\\mbfa" : "𝐚", - "\\mbfb" : "𝐛", - "\\mbfc" : "𝐜", - "\\mbfd" : "𝐝", - "\\mbfe" : "𝐞", - "\\mbff" : "𝐟", - "\\mbfg" : "𝐠", - "\\mbfh" : "𝐡", - "\\mbfi" : "𝐢", - "\\mbfj" : "𝐣", - "\\mbfk" : "𝐤", - "\\mbfl" : "𝐥", - "\\mbfm" : "𝐦", - "\\mbfn" : "𝐧", - "\\mbfo" : "𝐨", - "\\mbfp" : "𝐩", - "\\mbfq" : "𝐪", - "\\mbfr" : "𝐫", - "\\mbfs" : "𝐬", - "\\mbft" : "𝐭", - "\\mbfu" : "𝐮", - "\\mbfv" : "𝐯", - "\\mbfw" : "𝐰", - "\\mbfx" : "𝐱", - "\\mbfy" : "𝐲", - "\\mbfz" : "𝐳", - "\\mitA" : "𝐴", - "\\mitB" : "𝐵", - "\\mitC" : "𝐶", - "\\mitD" : "𝐷", - "\\mitE" : "𝐸", - "\\mitF" : "𝐹", - "\\mitG" : "𝐺", - "\\mitH" : "𝐻", - "\\mitI" : "𝐼", - "\\mitJ" : "𝐽", - "\\mitK" : "𝐾", - "\\mitL" : "𝐿", - "\\mitM" : "𝑀", - "\\mitN" : "𝑁", - "\\mitO" : "𝑂", - "\\mitP" : "𝑃", - "\\mitQ" : "𝑄", - "\\mitR" : "𝑅", - "\\mitS" : "𝑆", - "\\mitT" : "𝑇", - "\\mitU" : "𝑈", - "\\mitV" : "𝑉", - "\\mitW" : "𝑊", - "\\mitX" : "𝑋", - "\\mitY" : "𝑌", - "\\mitZ" : "𝑍", - "\\mita" : "𝑎", - "\\mitb" : "𝑏", - "\\mitc" : "𝑐", - "\\mitd" : "𝑑", - "\\mite" : "𝑒", - "\\mitf" : "𝑓", - "\\mitg" : "𝑔", - "\\miti" : "𝑖", - "\\mitj" : "𝑗", - "\\mitk" : "𝑘", - "\\mitl" : "𝑙", - "\\mitm" : "𝑚", - "\\mitn" : "𝑛", - "\\mito" : "𝑜", - "\\mitp" : "𝑝", - "\\mitq" : "𝑞", - "\\mitr" : "𝑟", - "\\mits" : "𝑠", - "\\mitt" : "𝑡", - "\\mitu" : "𝑢", - "\\mitv" : "𝑣", - "\\mitw" : "𝑤", - "\\mitx" : "𝑥", - "\\mity" : "𝑦", - "\\mitz" : "𝑧", - "\\mbfitA" : "𝑨", - "\\mbfitB" : "𝑩", - "\\mbfitC" : "𝑪", - "\\mbfitD" : "𝑫", - "\\mbfitE" : "𝑬", - "\\mbfitF" : "𝑭", - "\\mbfitG" : "𝑮", - "\\mbfitH" : "𝑯", - "\\mbfitI" : "𝑰", - "\\mbfitJ" : "𝑱", - "\\mbfitK" : "𝑲", - "\\mbfitL" : "𝑳", - "\\mbfitM" : "𝑴", - "\\mbfitN" : "𝑵", - "\\mbfitO" : "𝑶", - "\\mbfitP" : "𝑷", - "\\mbfitQ" : "𝑸", - "\\mbfitR" : "𝑹", - "\\mbfitS" : "𝑺", - "\\mbfitT" : "𝑻", - "\\mbfitU" : "𝑼", - "\\mbfitV" : "𝑽", - "\\mbfitW" : "𝑾", - "\\mbfitX" : "𝑿", - "\\mbfitY" : "𝒀", - "\\mbfitZ" : "𝒁", - "\\mbfita" : "𝒂", - "\\mbfitb" : "𝒃", - "\\mbfitc" : "𝒄", - "\\mbfitd" : "𝒅", - "\\mbfite" : "𝒆", - "\\mbfitf" : "𝒇", - "\\mbfitg" : "𝒈", - "\\mbfith" : "𝒉", - "\\mbfiti" : "𝒊", - "\\mbfitj" : "𝒋", - "\\mbfitk" : "𝒌", - "\\mbfitl" : "𝒍", - "\\mbfitm" : "𝒎", - "\\mbfitn" : "𝒏", - "\\mbfito" : "𝒐", - "\\mbfitp" : "𝒑", - "\\mbfitq" : "𝒒", - "\\mbfitr" : "𝒓", - "\\mbfits" : "𝒔", - "\\mbfitt" : "𝒕", - "\\mbfitu" : "𝒖", - "\\mbfitv" : "𝒗", - "\\mbfitw" : "𝒘", - "\\mbfitx" : "𝒙", - "\\mbfity" : "𝒚", - "\\mbfitz" : "𝒛", - "\\mscrA" : "𝒜", - "\\mscrC" : "𝒞", - "\\mscrD" : "𝒟", - "\\mscrG" : "𝒢", - "\\mscrJ" : "𝒥", - "\\mscrK" : "𝒦", - "\\mscrN" : "𝒩", - "\\mscrO" : "𝒪", - "\\mscrP" : "𝒫", - "\\mscrQ" : "𝒬", - "\\mscrS" : "𝒮", - "\\mscrT" : "𝒯", - "\\mscrU" : "𝒰", - "\\mscrV" : "𝒱", - "\\mscrW" : "𝒲", - "\\mscrX" : "𝒳", - "\\mscrY" : "𝒴", - "\\mscrZ" : "𝒵", - "\\mscra" : "𝒶", - "\\mscrb" : "𝒷", - "\\mscrc" : "𝒸", - "\\mscrd" : "𝒹", - "\\mscrf" : "𝒻", - "\\mscrh" : "𝒽", - "\\mscri" : "𝒾", - "\\mscrj" : "𝒿", - "\\mscrk" : "𝓀", - "\\mscrm" : "𝓂", - "\\mscrn" : "𝓃", - "\\mscrp" : "𝓅", - "\\mscrq" : "𝓆", - "\\mscrr" : "𝓇", - "\\mscrs" : "𝓈", - "\\mscrt" : "𝓉", - "\\mscru" : "𝓊", - "\\mscrv" : "𝓋", - "\\mscrw" : "𝓌", - "\\mscrx" : "𝓍", - "\\mscry" : "𝓎", - "\\mscrz" : "𝓏", - "\\mbfscrA" : "𝓐", - "\\mbfscrB" : "𝓑", - "\\mbfscrC" : "𝓒", - "\\mbfscrD" : "𝓓", - "\\mbfscrE" : "𝓔", - "\\mbfscrF" : "𝓕", - "\\mbfscrG" : "𝓖", - "\\mbfscrH" : "𝓗", - "\\mbfscrI" : "𝓘", - "\\mbfscrJ" : "𝓙", - "\\mbfscrK" : "𝓚", - "\\mbfscrL" : "𝓛", - "\\mbfscrM" : "𝓜", - "\\mbfscrN" : "𝓝", - "\\mbfscrO" : "𝓞", - "\\mbfscrP" : "𝓟", - "\\mbfscrQ" : "𝓠", - "\\mbfscrR" : "𝓡", - "\\mbfscrS" : "𝓢", - "\\mbfscrT" : "𝓣", - "\\mbfscrU" : "𝓤", - "\\mbfscrV" : "𝓥", - "\\mbfscrW" : "𝓦", - "\\mbfscrX" : "𝓧", - "\\mbfscrY" : "𝓨", - "\\mbfscrZ" : "𝓩", - "\\mbfscra" : "𝓪", - "\\mbfscrb" : "𝓫", - "\\mbfscrc" : "𝓬", - "\\mbfscrd" : "𝓭", - "\\mbfscre" : "𝓮", - "\\mbfscrf" : "𝓯", - "\\mbfscrg" : "𝓰", - "\\mbfscrh" : "𝓱", - "\\mbfscri" : "𝓲", - "\\mbfscrj" : "𝓳", - "\\mbfscrk" : "𝓴", - "\\mbfscrl" : "𝓵", - "\\mbfscrm" : "𝓶", - "\\mbfscrn" : "𝓷", - "\\mbfscro" : "𝓸", - "\\mbfscrp" : "𝓹", - "\\mbfscrq" : "𝓺", - "\\mbfscrr" : "𝓻", - "\\mbfscrs" : "𝓼", - "\\mbfscrt" : "𝓽", - "\\mbfscru" : "𝓾", - "\\mbfscrv" : "𝓿", - "\\mbfscrw" : "𝔀", - "\\mbfscrx" : "𝔁", - "\\mbfscry" : "𝔂", - "\\mbfscrz" : "𝔃", - "\\mfrakA" : "𝔄", - "\\mfrakB" : "𝔅", - "\\mfrakD" : "𝔇", - "\\mfrakE" : "𝔈", - "\\mfrakF" : "𝔉", - "\\mfrakG" : "𝔊", - "\\mfrakJ" : "𝔍", - "\\mfrakK" : "𝔎", - "\\mfrakL" : "𝔏", - "\\mfrakM" : "𝔐", - "\\mfrakN" : "𝔑", - "\\mfrakO" : "𝔒", - "\\mfrakP" : "𝔓", - "\\mfrakQ" : "𝔔", - "\\mfrakS" : "𝔖", - "\\mfrakT" : "𝔗", - "\\mfrakU" : "𝔘", - "\\mfrakV" : "𝔙", - "\\mfrakW" : "𝔚", - "\\mfrakX" : "𝔛", - "\\mfrakY" : "𝔜", - "\\mfraka" : "𝔞", - "\\mfrakb" : "𝔟", - "\\mfrakc" : "𝔠", - "\\mfrakd" : "𝔡", - "\\mfrake" : "𝔢", - "\\mfrakf" : "𝔣", - "\\mfrakg" : "𝔤", - "\\mfrakh" : "𝔥", - "\\mfraki" : "𝔦", - "\\mfrakj" : "𝔧", - "\\mfrakk" : "𝔨", - "\\mfrakl" : "𝔩", - "\\mfrakm" : "𝔪", - "\\mfrakn" : "𝔫", - "\\mfrako" : "𝔬", - "\\mfrakp" : "𝔭", - "\\mfrakq" : "𝔮", - "\\mfrakr" : "𝔯", - "\\mfraks" : "𝔰", - "\\mfrakt" : "𝔱", - "\\mfraku" : "𝔲", - "\\mfrakv" : "𝔳", - "\\mfrakw" : "𝔴", - "\\mfrakx" : "𝔵", - "\\mfraky" : "𝔶", - "\\mfrakz" : "𝔷", - "\\BbbA" : "𝔸", - "\\BbbB" : "𝔹", - "\\BbbD" : "𝔻", - "\\BbbE" : "𝔼", - "\\BbbF" : "𝔽", - "\\BbbG" : "𝔾", - "\\BbbI" : "𝕀", - "\\BbbJ" : "𝕁", - "\\BbbK" : "𝕂", - "\\BbbL" : "𝕃", - "\\BbbM" : "𝕄", - "\\BbbO" : "𝕆", - "\\BbbS" : "𝕊", - "\\BbbT" : "𝕋", - "\\BbbU" : "𝕌", - "\\BbbV" : "𝕍", - "\\BbbW" : "𝕎", - "\\BbbX" : "𝕏", - "\\BbbY" : "𝕐", - "\\Bbba" : "𝕒", - "\\Bbbb" : "𝕓", - "\\Bbbc" : "𝕔", - "\\Bbbd" : "𝕕", - "\\Bbbe" : "𝕖", - "\\Bbbf" : "𝕗", - "\\Bbbg" : "𝕘", - "\\Bbbh" : "𝕙", - "\\Bbbi" : "𝕚", - "\\Bbbj" : "𝕛", - "\\Bbbk" : "𝕜", - "\\Bbbl" : "𝕝", - "\\Bbbm" : "𝕞", - "\\Bbbn" : "𝕟", - "\\Bbbo" : "𝕠", - "\\Bbbp" : "𝕡", - "\\Bbbq" : "𝕢", - "\\Bbbr" : "𝕣", - "\\Bbbs" : "𝕤", - "\\Bbbt" : "𝕥", - "\\Bbbu" : "𝕦", - "\\Bbbv" : "𝕧", - "\\Bbbw" : "𝕨", - "\\Bbbx" : "𝕩", - "\\Bbby" : "𝕪", - "\\Bbbz" : "𝕫", - "\\mbffrakA" : "𝕬", - "\\mbffrakB" : "𝕭", - "\\mbffrakC" : "𝕮", - "\\mbffrakD" : "𝕯", - "\\mbffrakE" : "𝕰", - "\\mbffrakF" : "𝕱", - "\\mbffrakG" : "𝕲", - "\\mbffrakH" : "𝕳", - "\\mbffrakI" : "𝕴", - "\\mbffrakJ" : "𝕵", - "\\mbffrakK" : "𝕶", - "\\mbffrakL" : "𝕷", - "\\mbffrakM" : "𝕸", - "\\mbffrakN" : "𝕹", - "\\mbffrakO" : "𝕺", - "\\mbffrakP" : "𝕻", - "\\mbffrakQ" : "𝕼", - "\\mbffrakR" : "𝕽", - "\\mbffrakS" : "𝕾", - "\\mbffrakT" : "𝕿", - "\\mbffrakU" : "𝖀", - "\\mbffrakV" : "𝖁", - "\\mbffrakW" : "𝖂", - "\\mbffrakX" : "𝖃", - "\\mbffrakY" : "𝖄", - "\\mbffrakZ" : "𝖅", - "\\mbffraka" : "𝖆", - "\\mbffrakb" : "𝖇", - "\\mbffrakc" : "𝖈", - "\\mbffrakd" : "𝖉", - "\\mbffrake" : "𝖊", - "\\mbffrakf" : "𝖋", - "\\mbffrakg" : "𝖌", - "\\mbffrakh" : "𝖍", - "\\mbffraki" : "𝖎", - "\\mbffrakj" : "𝖏", - "\\mbffrakk" : "𝖐", - "\\mbffrakl" : "𝖑", - "\\mbffrakm" : "𝖒", - "\\mbffrakn" : "𝖓", - "\\mbffrako" : "𝖔", - "\\mbffrakp" : "𝖕", - "\\mbffrakq" : "𝖖", - "\\mbffrakr" : "𝖗", - "\\mbffraks" : "𝖘", - "\\mbffrakt" : "𝖙", - "\\mbffraku" : "𝖚", - "\\mbffrakv" : "𝖛", - "\\mbffrakw" : "𝖜", - "\\mbffrakx" : "𝖝", - "\\mbffraky" : "𝖞", - "\\mbffrakz" : "𝖟", - "\\msansA" : "𝖠", - "\\msansB" : "𝖡", - "\\msansC" : "𝖢", - "\\msansD" : "𝖣", - "\\msansE" : "𝖤", - "\\msansF" : "𝖥", - "\\msansG" : "𝖦", - "\\msansH" : "𝖧", - "\\msansI" : "𝖨", - "\\msansJ" : "𝖩", - "\\msansK" : "𝖪", - "\\msansL" : "𝖫", - "\\msansM" : "𝖬", - "\\msansN" : "𝖭", - "\\msansO" : "𝖮", - "\\msansP" : "𝖯", - "\\msansQ" : "𝖰", - "\\msansR" : "𝖱", - "\\msansS" : "𝖲", - "\\msansT" : "𝖳", - "\\msansU" : "𝖴", - "\\msansV" : "𝖵", - "\\msansW" : "𝖶", - "\\msansX" : "𝖷", - "\\msansY" : "𝖸", - "\\msansZ" : "𝖹", - "\\msansa" : "𝖺", - "\\msansb" : "𝖻", - "\\msansc" : "𝖼", - "\\msansd" : "𝖽", - "\\msanse" : "𝖾", - "\\msansf" : "𝖿", - "\\msansg" : "𝗀", - "\\msansh" : "𝗁", - "\\msansi" : "𝗂", - "\\msansj" : "𝗃", - "\\msansk" : "𝗄", - "\\msansl" : "𝗅", - "\\msansm" : "𝗆", - "\\msansn" : "𝗇", - "\\msanso" : "𝗈", - "\\msansp" : "𝗉", - "\\msansq" : "𝗊", - "\\msansr" : "𝗋", - "\\msanss" : "𝗌", - "\\msanst" : "𝗍", - "\\msansu" : "𝗎", - "\\msansv" : "𝗏", - "\\msansw" : "𝗐", - "\\msansx" : "𝗑", - "\\msansy" : "𝗒", - "\\msansz" : "𝗓", - "\\mbfsansA" : "𝗔", - "\\mbfsansB" : "𝗕", - "\\mbfsansC" : "𝗖", - "\\mbfsansD" : "𝗗", - "\\mbfsansE" : "𝗘", - "\\mbfsansF" : "𝗙", - "\\mbfsansG" : "𝗚", - "\\mbfsansH" : "𝗛", - "\\mbfsansI" : "𝗜", - "\\mbfsansJ" : "𝗝", - "\\mbfsansK" : "𝗞", - "\\mbfsansL" : "𝗟", - "\\mbfsansM" : "𝗠", - "\\mbfsansN" : "𝗡", - "\\mbfsansO" : "𝗢", - "\\mbfsansP" : "𝗣", - "\\mbfsansQ" : "𝗤", - "\\mbfsansR" : "𝗥", - "\\mbfsansS" : "𝗦", - "\\mbfsansT" : "𝗧", - "\\mbfsansU" : "𝗨", - "\\mbfsansV" : "𝗩", - "\\mbfsansW" : "𝗪", - "\\mbfsansX" : "𝗫", - "\\mbfsansY" : "𝗬", - "\\mbfsansZ" : "𝗭", - "\\mbfsansa" : "𝗮", - "\\mbfsansb" : "𝗯", - "\\mbfsansc" : "𝗰", - "\\mbfsansd" : "𝗱", - "\\mbfsanse" : "𝗲", - "\\mbfsansf" : "𝗳", - "\\mbfsansg" : "𝗴", - "\\mbfsansh" : "𝗵", - "\\mbfsansi" : "𝗶", - "\\mbfsansj" : "𝗷", - "\\mbfsansk" : "𝗸", - "\\mbfsansl" : "𝗹", - "\\mbfsansm" : "𝗺", - "\\mbfsansn" : "𝗻", - "\\mbfsanso" : "𝗼", - "\\mbfsansp" : "𝗽", - "\\mbfsansq" : "𝗾", - "\\mbfsansr" : "𝗿", - "\\mbfsanss" : "𝘀", - "\\mbfsanst" : "𝘁", - "\\mbfsansu" : "𝘂", - "\\mbfsansv" : "𝘃", - "\\mbfsansw" : "𝘄", - "\\mbfsansx" : "𝘅", - "\\mbfsansy" : "𝘆", - "\\mbfsansz" : "𝘇", - "\\mitsansA" : "𝘈", - "\\mitsansB" : "𝘉", - "\\mitsansC" : "𝘊", - "\\mitsansD" : "𝘋", - "\\mitsansE" : "𝘌", - "\\mitsansF" : "𝘍", - "\\mitsansG" : "𝘎", - "\\mitsansH" : "𝘏", - "\\mitsansI" : "𝘐", - "\\mitsansJ" : "𝘑", - "\\mitsansK" : "𝘒", - "\\mitsansL" : "𝘓", - "\\mitsansM" : "𝘔", - "\\mitsansN" : "𝘕", - "\\mitsansO" : "𝘖", - "\\mitsansP" : "𝘗", - "\\mitsansQ" : "𝘘", - "\\mitsansR" : "𝘙", - "\\mitsansS" : "𝘚", - "\\mitsansT" : "𝘛", - "\\mitsansU" : "𝘜", - "\\mitsansV" : "𝘝", - "\\mitsansW" : "𝘞", - "\\mitsansX" : "𝘟", - "\\mitsansY" : "𝘠", - "\\mitsansZ" : "𝘡", - "\\mitsansa" : "𝘢", - "\\mitsansb" : "𝘣", - "\\mitsansc" : "𝘤", - "\\mitsansd" : "𝘥", - "\\mitsanse" : "𝘦", - "\\mitsansf" : "𝘧", - "\\mitsansg" : "𝘨", - "\\mitsansh" : "𝘩", - "\\mitsansi" : "𝘪", - "\\mitsansj" : "𝘫", - "\\mitsansk" : "𝘬", - "\\mitsansl" : "𝘭", - "\\mitsansm" : "𝘮", - "\\mitsansn" : "𝘯", - "\\mitsanso" : "𝘰", - "\\mitsansp" : "𝘱", - "\\mitsansq" : "𝘲", - "\\mitsansr" : "𝘳", - "\\mitsanss" : "𝘴", - "\\mitsanst" : "𝘵", - "\\mitsansu" : "𝘶", - "\\mitsansv" : "𝘷", - "\\mitsansw" : "𝘸", - "\\mitsansx" : "𝘹", - "\\mitsansy" : "𝘺", - "\\mitsansz" : "𝘻", - "\\mbfitsansA" : "𝘼", - "\\mbfitsansB" : "𝘽", - "\\mbfitsansC" : "𝘾", - "\\mbfitsansD" : "𝘿", - "\\mbfitsansE" : "𝙀", - "\\mbfitsansF" : "𝙁", - "\\mbfitsansG" : "𝙂", - "\\mbfitsansH" : "𝙃", - "\\mbfitsansI" : "𝙄", - "\\mbfitsansJ" : "𝙅", - "\\mbfitsansK" : "𝙆", - "\\mbfitsansL" : "𝙇", - "\\mbfitsansM" : "𝙈", - "\\mbfitsansN" : "𝙉", - "\\mbfitsansO" : "𝙊", - "\\mbfitsansP" : "𝙋", - "\\mbfitsansQ" : "𝙌", - "\\mbfitsansR" : "𝙍", - "\\mbfitsansS" : "𝙎", - "\\mbfitsansT" : "𝙏", - "\\mbfitsansU" : "𝙐", - "\\mbfitsansV" : "𝙑", - "\\mbfitsansW" : "𝙒", - "\\mbfitsansX" : "𝙓", - "\\mbfitsansY" : "𝙔", - "\\mbfitsansZ" : "𝙕", - "\\mbfitsansa" : "𝙖", - "\\mbfitsansb" : "𝙗", - "\\mbfitsansc" : "𝙘", - "\\mbfitsansd" : "𝙙", - "\\mbfitsanse" : "𝙚", - "\\mbfitsansf" : "𝙛", - "\\mbfitsansg" : "𝙜", - "\\mbfitsansh" : "𝙝", - "\\mbfitsansi" : "𝙞", - "\\mbfitsansj" : "𝙟", - "\\mbfitsansk" : "𝙠", - "\\mbfitsansl" : "𝙡", - "\\mbfitsansm" : "𝙢", - "\\mbfitsansn" : "𝙣", - "\\mbfitsanso" : "𝙤", - "\\mbfitsansp" : "𝙥", - "\\mbfitsansq" : "𝙦", - "\\mbfitsansr" : "𝙧", - "\\mbfitsanss" : "𝙨", - "\\mbfitsanst" : "𝙩", - "\\mbfitsansu" : "𝙪", - "\\mbfitsansv" : "𝙫", - "\\mbfitsansw" : "𝙬", - "\\mbfitsansx" : "𝙭", - "\\mbfitsansy" : "𝙮", - "\\mbfitsansz" : "𝙯", - "\\mttA" : "𝙰", - "\\mttB" : "𝙱", - "\\mttC" : "𝙲", - "\\mttD" : "𝙳", - "\\mttE" : "𝙴", - "\\mttF" : "𝙵", - "\\mttG" : "𝙶", - "\\mttH" : "𝙷", - "\\mttI" : "𝙸", - "\\mttJ" : "𝙹", - "\\mttK" : "𝙺", - "\\mttL" : "𝙻", - "\\mttM" : "𝙼", - "\\mttN" : "𝙽", - "\\mttO" : "𝙾", - "\\mttP" : "𝙿", - "\\mttQ" : "𝚀", - "\\mttR" : "𝚁", - "\\mttS" : "𝚂", - "\\mttT" : "𝚃", - "\\mttU" : "𝚄", - "\\mttV" : "𝚅", - "\\mttW" : "𝚆", - "\\mttX" : "𝚇", - "\\mttY" : "𝚈", - "\\mttZ" : "𝚉", - "\\mtta" : "𝚊", - "\\mttb" : "𝚋", - "\\mttc" : "𝚌", - "\\mttd" : "𝚍", - "\\mtte" : "𝚎", - "\\mttf" : "𝚏", - "\\mttg" : "𝚐", - "\\mtth" : "𝚑", - "\\mtti" : "𝚒", - "\\mttj" : "𝚓", - "\\mttk" : "𝚔", - "\\mttl" : "𝚕", - "\\mttm" : "𝚖", - "\\mttn" : "𝚗", - "\\mtto" : "𝚘", - "\\mttp" : "𝚙", - "\\mttq" : "𝚚", - "\\mttr" : "𝚛", - "\\mtts" : "𝚜", - "\\mttt" : "𝚝", - "\\mttu" : "𝚞", - "\\mttv" : "𝚟", - "\\mttw" : "𝚠", - "\\mttx" : "𝚡", - "\\mtty" : "𝚢", - "\\mttz" : "𝚣", - "\\mbfAlpha" : "𝚨", - "\\mbfBeta" : "𝚩", - "\\mbfGamma" : "𝚪", - "\\mbfDelta" : "𝚫", - "\\mbfEpsilon" : "𝚬", - "\\mbfZeta" : "𝚭", - "\\mbfEta" : "𝚮", - "\\mbfTheta" : "𝚯", - "\\mbfIota" : "𝚰", - "\\mbfKappa" : "𝚱", - "\\mbfLambda" : "𝚲", - "\\mbfMu" : "𝚳", - "\\mbfNu" : "𝚴", - "\\mbfXi" : "𝚵", - "\\mbfOmicron" : "𝚶", - "\\mbfPi" : "𝚷", - "\\mbfRho" : "𝚸", - "\\mbfvarTheta" : "𝚹", - "\\mbfSigma" : "𝚺", - "\\mbfTau" : "𝚻", - "\\mbfUpsilon" : "𝚼", - "\\mbfPhi" : "𝚽", - "\\mbfChi" : "𝚾", - "\\mbfPsi" : "𝚿", - "\\mbfOmega" : "𝛀", - "\\mbfalpha" : "𝛂", - "\\mbfbeta" : "𝛃", - "\\mbfgamma" : "𝛄", - "\\mbfdelta" : "𝛅", - "\\mbfepsilon" : "𝛆", - "\\mbfzeta" : "𝛇", - "\\mbfeta" : "𝛈", - "\\mbftheta" : "𝛉", - "\\mbfiota" : "𝛊", - "\\mbfkappa" : "𝛋", - "\\mbflambda" : "𝛌", - "\\mbfmu" : "𝛍", - "\\mbfnu" : "𝛎", - "\\mbfxi" : "𝛏", - "\\mbfomicron" : "𝛐", - "\\mbfpi" : "𝛑", - "\\mbfrho" : "𝛒", - "\\mbfvarsigma" : "𝛓", - "\\mbfsigma" : "𝛔", - "\\mbftau" : "𝛕", - "\\mbfupsilon" : "𝛖", - "\\mbfvarphi" : "𝛗", - "\\mbfchi" : "𝛘", - "\\mbfpsi" : "𝛙", - "\\mbfomega" : "𝛚", - "\\mbfvarepsilon" : "𝛜", - "\\mbfvartheta" : "𝛝", - "\\mbfvarkappa" : "𝛞", - "\\mbfphi" : "𝛟", - "\\mbfvarrho" : "𝛠", - "\\mbfvarpi" : "𝛡", - "\\mitAlpha" : "𝛢", - "\\mitBeta" : "𝛣", - "\\mitGamma" : "𝛤", - "\\mitDelta" : "𝛥", - "\\mitEpsilon" : "𝛦", - "\\mitZeta" : "𝛧", - "\\mitEta" : "𝛨", - "\\mitTheta" : "𝛩", - "\\mitIota" : "𝛪", - "\\mitKappa" : "𝛫", - "\\mitLambda" : "𝛬", - "\\mitMu" : "𝛭", - "\\mitNu" : "𝛮", - "\\mitXi" : "𝛯", - "\\mitOmicron" : "𝛰", - "\\mitPi" : "𝛱", - "\\mitRho" : "𝛲", - "\\mitvarTheta" : "𝛳", - "\\mitSigma" : "𝛴", - "\\mitTau" : "𝛵", - "\\mitUpsilon" : "𝛶", - "\\mitPhi" : "𝛷", - "\\mitChi" : "𝛸", - "\\mitPsi" : "𝛹", - "\\mitOmega" : "𝛺", - "\\mitalpha" : "𝛼", - "\\mitbeta" : "𝛽", - "\\mitgamma" : "𝛾", - "\\mitdelta" : "𝛿", - "\\mitepsilon" : "𝜀", - "\\mitzeta" : "𝜁", - "\\miteta" : "𝜂", - "\\mittheta" : "𝜃", - "\\mitiota" : "𝜄", - "\\mitkappa" : "𝜅", - "\\mitlambda" : "𝜆", - "\\mitmu" : "𝜇", - "\\mitnu" : "𝜈", - "\\mitxi" : "𝜉", - "\\mitomicron" : "𝜊", - "\\mitpi" : "𝜋", - "\\mitrho" : "𝜌", - "\\mitvarsigma" : "𝜍", - "\\mitsigma" : "𝜎", - "\\mittau" : "𝜏", - "\\mitupsilon" : "𝜐", - "\\mitphi" : "𝜑", - "\\mitchi" : "𝜒", - "\\mitpsi" : "𝜓", - "\\mitomega" : "𝜔", - "\\mitvarepsilon" : "𝜖", - "\\mitvartheta" : "𝜗", - "\\mitvarkappa" : "𝜘", - "\\mitvarphi" : "𝜙", - "\\mitvarrho" : "𝜚", - "\\mitvarpi" : "𝜛", - "\\mbfitAlpha" : "𝜜", - "\\mbfitBeta" : "𝜝", - "\\mbfitGamma" : "𝜞", - "\\mbfitDelta" : "𝜟", - "\\mbfitEpsilon" : "𝜠", - "\\mbfitZeta" : "𝜡", - "\\mbfitEta" : "𝜢", - "\\mbfitTheta" : "𝜣", - "\\mbfitIota" : "𝜤", - "\\mbfitKappa" : "𝜥", - "\\mbfitLambda" : "𝜦", - "\\mbfitMu" : "𝜧", - "\\mbfitNu" : "𝜨", - "\\mbfitXi" : "𝜩", - "\\mbfitOmicron" : "𝜪", - "\\mbfitPi" : "𝜫", - "\\mbfitRho" : "𝜬", - "\\mbfitvarTheta" : "𝜭", - "\\mbfitSigma" : "𝜮", - "\\mbfitTau" : "𝜯", - "\\mbfitUpsilon" : "𝜰", - "\\mbfitPhi" : "𝜱", - "\\mbfitChi" : "𝜲", - "\\mbfitPsi" : "𝜳", - "\\mbfitOmega" : "𝜴", - "\\mbfitalpha" : "𝜶", - "\\mbfitbeta" : "𝜷", - "\\mbfitgamma" : "𝜸", - "\\mbfitdelta" : "𝜹", - "\\mbfitepsilon" : "𝜺", - "\\mbfitzeta" : "𝜻", - "\\mbfiteta" : "𝜼", - "\\mbfittheta" : "𝜽", - "\\mbfitiota" : "𝜾", - "\\mbfitkappa" : "𝜿", - "\\mbfitlambda" : "𝝀", - "\\mbfitmu" : "𝝁", - "\\mbfitnu" : "𝝂", - "\\mbfitxi" : "𝝃", - "\\mbfitomicron" : "𝝄", - "\\mbfitpi" : "𝝅", - "\\mbfitrho" : "𝝆", - "\\mbfitvarsigma" : "𝝇", - "\\mbfitsigma" : "𝝈", - "\\mbfittau" : "𝝉", - "\\mbfitupsilon" : "𝝊", - "\\mbfitphi" : "𝝋", - "\\mbfitchi" : "𝝌", - "\\mbfitpsi" : "𝝍", - "\\mbfitomega" : "𝝎", - "\\mbfitvarepsilon" : "𝝐", - "\\mbfitvartheta" : "𝝑", - "\\mbfitvarkappa" : "𝝒", - "\\mbfitvarphi" : "𝝓", - "\\mbfitvarrho" : "𝝔", - "\\mbfitvarpi" : "𝝕", - "\\mbfsansAlpha" : "𝝖", - "\\mbfsansBeta" : "𝝗", - "\\mbfsansGamma" : "𝝘", - "\\mbfsansDelta" : "𝝙", - "\\mbfsansEpsilon" : "𝝚", - "\\mbfsansZeta" : "𝝛", - "\\mbfsansEta" : "𝝜", - "\\mbfsansTheta" : "𝝝", - "\\mbfsansIota" : "𝝞", - "\\mbfsansKappa" : "𝝟", - "\\mbfsansLambda" : "𝝠", - "\\mbfsansMu" : "𝝡", - "\\mbfsansNu" : "𝝢", - "\\mbfsansXi" : "𝝣", - "\\mbfsansOmicron" : "𝝤", - "\\mbfsansPi" : "𝝥", - "\\mbfsansRho" : "𝝦", - "\\mbfsansvarTheta" : "𝝧", - "\\mbfsansSigma" : "𝝨", - "\\mbfsansTau" : "𝝩", - "\\mbfsansUpsilon" : "𝝪", - "\\mbfsansPhi" : "𝝫", - "\\mbfsansChi" : "𝝬", - "\\mbfsansPsi" : "𝝭", - "\\mbfsansOmega" : "𝝮", - "\\mbfsansalpha" : "𝝰", - "\\mbfsansbeta" : "𝝱", - "\\mbfsansgamma" : "𝝲", - "\\mbfsansdelta" : "𝝳", - "\\mbfsansepsilon" : "𝝴", - "\\mbfsanszeta" : "𝝵", - "\\mbfsanseta" : "𝝶", - "\\mbfsanstheta" : "𝝷", - "\\mbfsansiota" : "𝝸", - "\\mbfsanskappa" : "𝝹", - "\\mbfsanslambda" : "𝝺", - "\\mbfsansmu" : "𝝻", - "\\mbfsansnu" : "𝝼", - "\\mbfsansxi" : "𝝽", - "\\mbfsansomicron" : "𝝾", - "\\mbfsanspi" : "𝝿", - "\\mbfsansrho" : "𝞀", - "\\mbfsansvarsigma" : "𝞁", - "\\mbfsanssigma" : "𝞂", - "\\mbfsanstau" : "𝞃", - "\\mbfsansupsilon" : "𝞄", - "\\mbfsansphi" : "𝞅", - "\\mbfsanschi" : "𝞆", - "\\mbfsanspsi" : "𝞇", - "\\mbfsansomega" : "𝞈", - "\\mbfsansvarepsilon" : "𝞊", - "\\mbfsansvartheta" : "𝞋", - "\\mbfsansvarkappa" : "𝞌", - "\\mbfsansvarphi" : "𝞍", - "\\mbfsansvarrho" : "𝞎", - "\\mbfsansvarpi" : "𝞏", - "\\mbfitsansAlpha" : "𝞐", - "\\mbfitsansBeta" : "𝞑", - "\\mbfitsansGamma" : "𝞒", - "\\mbfitsansDelta" : "𝞓", - "\\mbfitsansEpsilon" : "𝞔", - "\\mbfitsansZeta" : "𝞕", - "\\mbfitsansEta" : "𝞖", - "\\mbfitsansTheta" : "𝞗", - "\\mbfitsansIota" : "𝞘", - "\\mbfitsansKappa" : "𝞙", - "\\mbfitsansLambda" : "𝞚", - "\\mbfitsansMu" : "𝞛", - "\\mbfitsansNu" : "𝞜", - "\\mbfitsansXi" : "𝞝", - "\\mbfitsansOmicron" : "𝞞", - "\\mbfitsansPi" : "𝞟", - "\\mbfitsansRho" : "𝞠", - "\\mbfitsansvarTheta" : "𝞡", - "\\mbfitsansSigma" : "𝞢", - "\\mbfitsansTau" : "𝞣", - "\\mbfitsansUpsilon" : "𝞤", - "\\mbfitsansPhi" : "𝞥", - "\\mbfitsansChi" : "𝞦", - "\\mbfitsansPsi" : "𝞧", - "\\mbfitsansOmega" : "𝞨", - "\\mbfitsansalpha" : "𝞪", - "\\mbfitsansbeta" : "𝞫", - "\\mbfitsansgamma" : "𝞬", - "\\mbfitsansdelta" : "𝞭", - "\\mbfitsansepsilon" : "𝞮", - "\\mbfitsanszeta" : "𝞯", - "\\mbfitsanseta" : "𝞰", - "\\mbfitsanstheta" : "𝞱", - "\\mbfitsansiota" : "𝞲", - "\\mbfitsanskappa" : "𝞳", - "\\mbfitsanslambda" : "𝞴", - "\\mbfitsansmu" : "𝞵", - "\\mbfitsansnu" : "𝞶", - "\\mbfitsansxi" : "𝞷", - "\\mbfitsansomicron" : "𝞸", - "\\mbfitsanspi" : "𝞹", - "\\mbfitsansrho" : "𝞺", - "\\mbfitsansvarsigma" : "𝞻", - "\\mbfitsanssigma" : "𝞼", - "\\mbfitsanstau" : "𝞽", - "\\mbfitsansupsilon" : "𝞾", - "\\mbfitsansphi" : "𝞿", - "\\mbfitsanschi" : "𝟀", - "\\mbfitsanspsi" : "𝟁", - "\\mbfitsansomega" : "𝟂", - "\\mbfitsansvarepsilon" : "𝟄", - "\\mbfitsansvartheta" : "𝟅", - "\\mbfitsansvarkappa" : "𝟆", - "\\mbfitsansvarphi" : "𝟇", - "\\mbfitsansvarrho" : "𝟈", - "\\mbfitsansvarpi" : "𝟉", - "\\mbfzero" : "𝟎", - "\\mbfone" : "𝟏", - "\\mbftwo" : "𝟐", - "\\mbfthree" : "𝟑", - "\\mbffour" : "𝟒", - "\\mbffive" : "𝟓", - "\\mbfsix" : "𝟔", - "\\mbfseven" : "𝟕", - "\\mbfeight" : "𝟖", - "\\mbfnine" : "𝟗", - "\\Bbbzero" : "𝟘", - "\\Bbbone" : "𝟙", - "\\Bbbtwo" : "𝟚", - "\\Bbbthree" : "𝟛", - "\\Bbbfour" : "𝟜", - "\\Bbbfive" : "𝟝", - "\\Bbbsix" : "𝟞", - "\\Bbbseven" : "𝟟", - "\\Bbbeight" : "𝟠", - "\\Bbbnine" : "𝟡", - "\\msanszero" : "𝟢", - "\\msansone" : "𝟣", - "\\msanstwo" : "𝟤", - "\\msansthree" : "𝟥", - "\\msansfour" : "𝟦", - "\\msansfive" : "𝟧", - "\\msanssix" : "𝟨", - "\\msansseven" : "𝟩", - "\\msanseight" : "𝟪", - "\\msansnine" : "𝟫", - "\\mbfsanszero" : "𝟬", - "\\mbfsansone" : "𝟭", - "\\mbfsanstwo" : "𝟮", - "\\mbfsansthree" : "𝟯", - "\\mbfsansfour" : "𝟰", - "\\mbfsansfive" : "𝟱", - "\\mbfsanssix" : "𝟲", - "\\mbfsansseven" : "𝟳", - "\\mbfsanseight" : "𝟴", - "\\mbfsansnine" : "𝟵", - "\\mttzero" : "𝟶", - "\\mttone" : "𝟷", - "\\mtttwo" : "𝟸", - "\\mttthree" : "𝟹", - "\\mttfour" : "𝟺", - "\\mttfive" : "𝟻", - "\\mttsix" : "𝟼", - "\\mttseven" : "𝟽", - "\\mtteight" : "𝟾", - "\\mttnine" : "𝟿", + "\\scrM" : "ℳ", + "\\scro" : "ℴ", + "\\bbgamma" : "ℽ", + "\\bbGamma" : "ℾ", + "\\bbiD" : "ⅅ", + "\\bbid" : "ⅆ", + "\\bbie" : "ⅇ", + "\\bbii" : "ⅈ", + "\\bbij" : "ⅉ", + "\\bfA" : "𝐀", + "\\bfB" : "𝐁", + "\\bfC" : "𝐂", + "\\bfD" : "𝐃", + "\\bfE" : "𝐄", + "\\bfF" : "𝐅", + "\\bfG" : "𝐆", + "\\bfH" : "𝐇", + "\\bfI" : "𝐈", + "\\bfJ" : "𝐉", + "\\bfK" : "𝐊", + "\\bfL" : "𝐋", + "\\bfM" : "𝐌", + "\\bfN" : "𝐍", + "\\bfO" : "𝐎", + "\\bfP" : "𝐏", + "\\bfQ" : "𝐐", + "\\bfR" : "𝐑", + "\\bfS" : "𝐒", + "\\bfT" : "𝐓", + "\\bfU" : "𝐔", + "\\bfV" : "𝐕", + "\\bfW" : "𝐖", + "\\bfX" : "𝐗", + "\\bfY" : "𝐘", + "\\bfZ" : "𝐙", + "\\bfa" : "𝐚", + "\\bfb" : "𝐛", + "\\bfc" : "𝐜", + "\\bfd" : "𝐝", + "\\bfe" : "𝐞", + "\\bff" : "𝐟", + "\\bfg" : "𝐠", + "\\bfh" : "𝐡", + "\\bfi" : "𝐢", + "\\bfj" : "𝐣", + "\\bfk" : "𝐤", + "\\bfl" : "𝐥", + "\\bfm" : "𝐦", + "\\bfn" : "𝐧", + "\\bfo" : "𝐨", + "\\bfp" : "𝐩", + "\\bfq" : "𝐪", + "\\bfr" : "𝐫", + "\\bfs" : "𝐬", + "\\bft" : "𝐭", + "\\bfu" : "𝐮", + "\\bfv" : "𝐯", + "\\bfw" : "𝐰", + "\\bfx" : "𝐱", + "\\bfy" : "𝐲", + "\\bfz" : "𝐳", + "\\itA" : "𝐴", + "\\itB" : "𝐵", + "\\itC" : "𝐶", + "\\itD" : "𝐷", + "\\itE" : "𝐸", + "\\itF" : "𝐹", + "\\itG" : "𝐺", + "\\itH" : "𝐻", + "\\itI" : "𝐼", + "\\itJ" : "𝐽", + "\\itK" : "𝐾", + "\\itL" : "𝐿", + "\\itM" : "𝑀", + "\\itN" : "𝑁", + "\\itO" : "𝑂", + "\\itP" : "𝑃", + "\\itQ" : "𝑄", + "\\itR" : "𝑅", + "\\itS" : "𝑆", + "\\itT" : "𝑇", + "\\itU" : "𝑈", + "\\itV" : "𝑉", + "\\itW" : "𝑊", + "\\itX" : "𝑋", + "\\itY" : "𝑌", + "\\itZ" : "𝑍", + "\\ita" : "𝑎", + "\\itb" : "𝑏", + "\\itc" : "𝑐", + "\\itd" : "𝑑", + "\\ite" : "𝑒", + "\\itf" : "𝑓", + "\\itg" : "𝑔", + "\\iti" : "𝑖", + "\\itj" : "𝑗", + "\\itk" : "𝑘", + "\\itl" : "𝑙", + "\\itm" : "𝑚", + "\\itn" : "𝑛", + "\\ito" : "𝑜", + "\\itp" : "𝑝", + "\\itq" : "𝑞", + "\\itr" : "𝑟", + "\\its" : "𝑠", + "\\itt" : "𝑡", + "\\itu" : "𝑢", + "\\itv" : "𝑣", + "\\itw" : "𝑤", + "\\itx" : "𝑥", + "\\ity" : "𝑦", + "\\itz" : "𝑧", + "\\biA" : "𝑨", + "\\biB" : "𝑩", + "\\biC" : "𝑪", + "\\biD" : "𝑫", + "\\biE" : "𝑬", + "\\biF" : "𝑭", + "\\biG" : "𝑮", + "\\biH" : "𝑯", + "\\biI" : "𝑰", + "\\biJ" : "𝑱", + "\\biK" : "𝑲", + "\\biL" : "𝑳", + "\\biM" : "𝑴", + "\\biN" : "𝑵", + "\\biO" : "𝑶", + "\\biP" : "𝑷", + "\\biQ" : "𝑸", + "\\biR" : "𝑹", + "\\biS" : "𝑺", + "\\biT" : "𝑻", + "\\biU" : "𝑼", + "\\biV" : "𝑽", + "\\biW" : "𝑾", + "\\biX" : "𝑿", + "\\biY" : "𝒀", + "\\biZ" : "𝒁", + "\\bia" : "𝒂", + "\\bib" : "𝒃", + "\\bic" : "𝒄", + "\\bid" : "𝒅", + "\\bie" : "𝒆", + "\\bif" : "𝒇", + "\\big" : "𝒈", + "\\bih" : "𝒉", + "\\bii" : "𝒊", + "\\bij" : "𝒋", + "\\bik" : "𝒌", + "\\bil" : "𝒍", + "\\bim" : "𝒎", + "\\bin" : "𝒏", + "\\bio" : "𝒐", + "\\bip" : "𝒑", + "\\biq" : "𝒒", + "\\bir" : "𝒓", + "\\bis" : "𝒔", + "\\bit" : "𝒕", + "\\biu" : "𝒖", + "\\biv" : "𝒗", + "\\biw" : "𝒘", + "\\bix" : "𝒙", + "\\biy" : "𝒚", + "\\biz" : "𝒛", + "\\scrA" : "𝒜", + "\\scrC" : "𝒞", + "\\scrD" : "𝒟", + "\\scrG" : "𝒢", + "\\scrJ" : "𝒥", + "\\scrK" : "𝒦", + "\\scrN" : "𝒩", + "\\scrO" : "𝒪", + "\\scrP" : "𝒫", + "\\scrQ" : "𝒬", + "\\scrS" : "𝒮", + "\\scrT" : "𝒯", + "\\scrU" : "𝒰", + "\\scrV" : "𝒱", + "\\scrW" : "𝒲", + "\\scrX" : "𝒳", + "\\scrY" : "𝒴", + "\\scrZ" : "𝒵", + "\\scra" : "𝒶", + "\\scrb" : "𝒷", + "\\scrc" : "𝒸", + "\\scrd" : "𝒹", + "\\scrf" : "𝒻", + "\\scrh" : "𝒽", + "\\scri" : "𝒾", + "\\scrj" : "𝒿", + "\\scrk" : "𝓀", + "\\scrm" : "𝓂", + "\\scrn" : "𝓃", + "\\scrp" : "𝓅", + "\\scrq" : "𝓆", + "\\scrr" : "𝓇", + "\\scrs" : "𝓈", + "\\scrt" : "𝓉", + "\\scru" : "𝓊", + "\\scrv" : "𝓋", + "\\scrw" : "𝓌", + "\\scrx" : "𝓍", + "\\scry" : "𝓎", + "\\scrz" : "𝓏", + "\\bscrA" : "𝓐", + "\\bscrB" : "𝓑", + "\\bscrC" : "𝓒", + "\\bscrD" : "𝓓", + "\\bscrE" : "𝓔", + "\\bscrF" : "𝓕", + "\\bscrG" : "𝓖", + "\\bscrH" : "𝓗", + "\\bscrI" : "𝓘", + "\\bscrJ" : "𝓙", + "\\bscrK" : "𝓚", + "\\bscrL" : "𝓛", + "\\bscrM" : "𝓜", + "\\bscrN" : "𝓝", + "\\bscrO" : "𝓞", + "\\bscrP" : "𝓟", + "\\bscrQ" : "𝓠", + "\\bscrR" : "𝓡", + "\\bscrS" : "𝓢", + "\\bscrT" : "𝓣", + "\\bscrU" : "𝓤", + "\\bscrV" : "𝓥", + "\\bscrW" : "𝓦", + "\\bscrX" : "𝓧", + "\\bscrY" : "𝓨", + "\\bscrZ" : "𝓩", + "\\bscra" : "𝓪", + "\\bscrb" : "𝓫", + "\\bscrc" : "𝓬", + "\\bscrd" : "𝓭", + "\\bscre" : "𝓮", + "\\bscrf" : "𝓯", + "\\bscrg" : "𝓰", + "\\bscrh" : "𝓱", + "\\bscri" : "𝓲", + "\\bscrj" : "𝓳", + "\\bscrk" : "𝓴", + "\\bscrl" : "𝓵", + "\\bscrm" : "𝓶", + "\\bscrn" : "𝓷", + "\\bscro" : "𝓸", + "\\bscrp" : "𝓹", + "\\bscrq" : "𝓺", + "\\bscrr" : "𝓻", + "\\bscrs" : "𝓼", + "\\bscrt" : "𝓽", + "\\bscru" : "𝓾", + "\\bscrv" : "𝓿", + "\\bscrw" : "𝔀", + "\\bscrx" : "𝔁", + "\\bscry" : "𝔂", + "\\bscrz" : "𝔃", + "\\frakA" : "𝔄", + "\\frakB" : "𝔅", + "\\frakD" : "𝔇", + "\\frakE" : "𝔈", + "\\frakF" : "𝔉", + "\\frakG" : "𝔊", + "\\frakJ" : "𝔍", + "\\frakK" : "𝔎", + "\\frakL" : "𝔏", + "\\frakM" : "𝔐", + "\\frakN" : "𝔑", + "\\frakO" : "𝔒", + "\\frakP" : "𝔓", + "\\frakQ" : "𝔔", + "\\frakS" : "𝔖", + "\\frakT" : "𝔗", + "\\frakU" : "𝔘", + "\\frakV" : "𝔙", + "\\frakW" : "𝔚", + "\\frakX" : "𝔛", + "\\frakY" : "𝔜", + "\\fraka" : "𝔞", + "\\frakb" : "𝔟", + "\\frakc" : "𝔠", + "\\frakd" : "𝔡", + "\\frake" : "𝔢", + "\\frakf" : "𝔣", + "\\frakg" : "𝔤", + "\\frakh" : "𝔥", + "\\fraki" : "𝔦", + "\\frakj" : "𝔧", + "\\frakk" : "𝔨", + "\\frakl" : "𝔩", + "\\frakm" : "𝔪", + "\\frakn" : "𝔫", + "\\frako" : "𝔬", + "\\frakp" : "𝔭", + "\\frakq" : "𝔮", + "\\frakr" : "𝔯", + "\\fraks" : "𝔰", + "\\frakt" : "𝔱", + "\\fraku" : "𝔲", + "\\frakv" : "𝔳", + "\\frakw" : "𝔴", + "\\frakx" : "𝔵", + "\\fraky" : "𝔶", + "\\frakz" : "𝔷", + "\\bbA" : "𝔸", + "\\bbB" : "𝔹", + "\\bbD" : "𝔻", + "\\bbE" : "𝔼", + "\\bbF" : "𝔽", + "\\bbG" : "𝔾", + "\\bbI" : "𝕀", + "\\bbJ" : "𝕁", + "\\bbK" : "𝕂", + "\\bbL" : "𝕃", + "\\bbM" : "𝕄", + "\\bbO" : "𝕆", + "\\bbS" : "𝕊", + "\\bbT" : "𝕋", + "\\bbU" : "𝕌", + "\\bbV" : "𝕍", + "\\bbW" : "𝕎", + "\\bbX" : "𝕏", + "\\bbY" : "𝕐", + "\\bba" : "𝕒", + "\\bbb" : "𝕓", + "\\bbc" : "𝕔", + "\\bbd" : "𝕕", + "\\bbe" : "𝕖", + "\\bbf" : "𝕗", + "\\bbg" : "𝕘", + "\\bbh" : "𝕙", + "\\bbi" : "𝕚", + "\\bbj" : "𝕛", + "\\bbk" : "𝕜", + "\\bbl" : "𝕝", + "\\bbm" : "𝕞", + "\\bbn" : "𝕟", + "\\bbo" : "𝕠", + "\\bbp" : "𝕡", + "\\bbq" : "𝕢", + "\\bbr" : "𝕣", + "\\bbs" : "𝕤", + "\\bbt" : "𝕥", + "\\bbu" : "𝕦", + "\\bbv" : "𝕧", + "\\bbw" : "𝕨", + "\\bbx" : "𝕩", + "\\bby" : "𝕪", + "\\bbz" : "𝕫", + "\\bfrakA" : "𝕬", + "\\bfrakB" : "𝕭", + "\\bfrakC" : "𝕮", + "\\bfrakD" : "𝕯", + "\\bfrakE" : "𝕰", + "\\bfrakF" : "𝕱", + "\\bfrakG" : "𝕲", + "\\bfrakH" : "𝕳", + "\\bfrakI" : "𝕴", + "\\bfrakJ" : "𝕵", + "\\bfrakK" : "𝕶", + "\\bfrakL" : "𝕷", + "\\bfrakM" : "𝕸", + "\\bfrakN" : "𝕹", + "\\bfrakO" : "𝕺", + "\\bfrakP" : "𝕻", + "\\bfrakQ" : "𝕼", + "\\bfrakR" : "𝕽", + "\\bfrakS" : "𝕾", + "\\bfrakT" : "𝕿", + "\\bfrakU" : "𝖀", + "\\bfrakV" : "𝖁", + "\\bfrakW" : "𝖂", + "\\bfrakX" : "𝖃", + "\\bfrakY" : "𝖄", + "\\bfrakZ" : "𝖅", + "\\bfraka" : "𝖆", + "\\bfrakb" : "𝖇", + "\\bfrakc" : "𝖈", + "\\bfrakd" : "𝖉", + "\\bfrake" : "𝖊", + "\\bfrakf" : "𝖋", + "\\bfrakg" : "𝖌", + "\\bfrakh" : "𝖍", + "\\bfraki" : "𝖎", + "\\bfrakj" : "𝖏", + "\\bfrakk" : "𝖐", + "\\bfrakl" : "𝖑", + "\\bfrakm" : "𝖒", + "\\bfrakn" : "𝖓", + "\\bfrako" : "𝖔", + "\\bfrakp" : "𝖕", + "\\bfrakq" : "𝖖", + "\\bfrakr" : "𝖗", + "\\bfraks" : "𝖘", + "\\bfrakt" : "𝖙", + "\\bfraku" : "𝖚", + "\\bfrakv" : "𝖛", + "\\bfrakw" : "𝖜", + "\\bfrakx" : "𝖝", + "\\bfraky" : "𝖞", + "\\bfrakz" : "𝖟", + "\\sansA" : "𝖠", + "\\sansB" : "𝖡", + "\\sansC" : "𝖢", + "\\sansD" : "𝖣", + "\\sansE" : "𝖤", + "\\sansF" : "𝖥", + "\\sansG" : "𝖦", + "\\sansH" : "𝖧", + "\\sansI" : "𝖨", + "\\sansJ" : "𝖩", + "\\sansK" : "𝖪", + "\\sansL" : "𝖫", + "\\sansM" : "𝖬", + "\\sansN" : "𝖭", + "\\sansO" : "𝖮", + "\\sansP" : "𝖯", + "\\sansQ" : "𝖰", + "\\sansR" : "𝖱", + "\\sansS" : "𝖲", + "\\sansT" : "𝖳", + "\\sansU" : "𝖴", + "\\sansV" : "𝖵", + "\\sansW" : "𝖶", + "\\sansX" : "𝖷", + "\\sansY" : "𝖸", + "\\sansZ" : "𝖹", + "\\sansa" : "𝖺", + "\\sansb" : "𝖻", + "\\sansc" : "𝖼", + "\\sansd" : "𝖽", + "\\sanse" : "𝖾", + "\\sansf" : "𝖿", + "\\sansg" : "𝗀", + "\\sansh" : "𝗁", + "\\sansi" : "𝗂", + "\\sansj" : "𝗃", + "\\sansk" : "𝗄", + "\\sansl" : "𝗅", + "\\sansm" : "𝗆", + "\\sansn" : "𝗇", + "\\sanso" : "𝗈", + "\\sansp" : "𝗉", + "\\sansq" : "𝗊", + "\\sansr" : "𝗋", + "\\sanss" : "𝗌", + "\\sanst" : "𝗍", + "\\sansu" : "𝗎", + "\\sansv" : "𝗏", + "\\sansw" : "𝗐", + "\\sansx" : "𝗑", + "\\sansy" : "𝗒", + "\\sansz" : "𝗓", + "\\bsansA" : "𝗔", + "\\bsansB" : "𝗕", + "\\bsansC" : "𝗖", + "\\bsansD" : "𝗗", + "\\bsansE" : "𝗘", + "\\bsansF" : "𝗙", + "\\bsansG" : "𝗚", + "\\bsansH" : "𝗛", + "\\bsansI" : "𝗜", + "\\bsansJ" : "𝗝", + "\\bsansK" : "𝗞", + "\\bsansL" : "𝗟", + "\\bsansM" : "𝗠", + "\\bsansN" : "𝗡", + "\\bsansO" : "𝗢", + "\\bsansP" : "𝗣", + "\\bsansQ" : "𝗤", + "\\bsansR" : "𝗥", + "\\bsansS" : "𝗦", + "\\bsansT" : "𝗧", + "\\bsansU" : "𝗨", + "\\bsansV" : "𝗩", + "\\bsansW" : "𝗪", + "\\bsansX" : "𝗫", + "\\bsansY" : "𝗬", + "\\bsansZ" : "𝗭", + "\\bsansa" : "𝗮", + "\\bsansb" : "𝗯", + "\\bsansc" : "𝗰", + "\\bsansd" : "𝗱", + "\\bsanse" : "𝗲", + "\\bsansf" : "𝗳", + "\\bsansg" : "𝗴", + "\\bsansh" : "𝗵", + "\\bsansi" : "𝗶", + "\\bsansj" : "𝗷", + "\\bsansk" : "𝗸", + "\\bsansl" : "𝗹", + "\\bsansm" : "𝗺", + "\\bsansn" : "𝗻", + "\\bsanso" : "𝗼", + "\\bsansp" : "𝗽", + "\\bsansq" : "𝗾", + "\\bsansr" : "𝗿", + "\\bsanss" : "𝘀", + "\\bsanst" : "𝘁", + "\\bsansu" : "𝘂", + "\\bsansv" : "𝘃", + "\\bsansw" : "𝘄", + "\\bsansx" : "𝘅", + "\\bsansy" : "𝘆", + "\\bsansz" : "𝘇", + "\\isansA" : "𝘈", + "\\isansB" : "𝘉", + "\\isansC" : "𝘊", + "\\isansD" : "𝘋", + "\\isansE" : "𝘌", + "\\isansF" : "𝘍", + "\\isansG" : "𝘎", + "\\isansH" : "𝘏", + "\\isansI" : "𝘐", + "\\isansJ" : "𝘑", + "\\isansK" : "𝘒", + "\\isansL" : "𝘓", + "\\isansM" : "𝘔", + "\\isansN" : "𝘕", + "\\isansO" : "𝘖", + "\\isansP" : "𝘗", + "\\isansQ" : "𝘘", + "\\isansR" : "𝘙", + "\\isansS" : "𝘚", + "\\isansT" : "𝘛", + "\\isansU" : "𝘜", + "\\isansV" : "𝘝", + "\\isansW" : "𝘞", + "\\isansX" : "𝘟", + "\\isansY" : "𝘠", + "\\isansZ" : "𝘡", + "\\isansa" : "𝘢", + "\\isansb" : "𝘣", + "\\isansc" : "𝘤", + "\\isansd" : "𝘥", + "\\isanse" : "𝘦", + "\\isansf" : "𝘧", + "\\isansg" : "𝘨", + "\\isansh" : "𝘩", + "\\isansi" : "𝘪", + "\\isansj" : "𝘫", + "\\isansk" : "𝘬", + "\\isansl" : "𝘭", + "\\isansm" : "𝘮", + "\\isansn" : "𝘯", + "\\isanso" : "𝘰", + "\\isansp" : "𝘱", + "\\isansq" : "𝘲", + "\\isansr" : "𝘳", + "\\isanss" : "𝘴", + "\\isanst" : "𝘵", + "\\isansu" : "𝘶", + "\\isansv" : "𝘷", + "\\isansw" : "𝘸", + "\\isansx" : "𝘹", + "\\isansy" : "𝘺", + "\\isansz" : "𝘻", + "\\bisansA" : "𝘼", + "\\bisansB" : "𝘽", + "\\bisansC" : "𝘾", + "\\bisansD" : "𝘿", + "\\bisansE" : "𝙀", + "\\bisansF" : "𝙁", + "\\bisansG" : "𝙂", + "\\bisansH" : "𝙃", + "\\bisansI" : "𝙄", + "\\bisansJ" : "𝙅", + "\\bisansK" : "𝙆", + "\\bisansL" : "𝙇", + "\\bisansM" : "𝙈", + "\\bisansN" : "𝙉", + "\\bisansO" : "𝙊", + "\\bisansP" : "𝙋", + "\\bisansQ" : "𝙌", + "\\bisansR" : "𝙍", + "\\bisansS" : "𝙎", + "\\bisansT" : "𝙏", + "\\bisansU" : "𝙐", + "\\bisansV" : "𝙑", + "\\bisansW" : "𝙒", + "\\bisansX" : "𝙓", + "\\bisansY" : "𝙔", + "\\bisansZ" : "𝙕", + "\\bisansa" : "𝙖", + "\\bisansb" : "𝙗", + "\\bisansc" : "𝙘", + "\\bisansd" : "𝙙", + "\\bisanse" : "𝙚", + "\\bisansf" : "𝙛", + "\\bisansg" : "𝙜", + "\\bisansh" : "𝙝", + "\\bisansi" : "𝙞", + "\\bisansj" : "𝙟", + "\\bisansk" : "𝙠", + "\\bisansl" : "𝙡", + "\\bisansm" : "𝙢", + "\\bisansn" : "𝙣", + "\\bisanso" : "𝙤", + "\\bisansp" : "𝙥", + "\\bisansq" : "𝙦", + "\\bisansr" : "𝙧", + "\\bisanss" : "𝙨", + "\\bisanst" : "𝙩", + "\\bisansu" : "𝙪", + "\\bisansv" : "𝙫", + "\\bisansw" : "𝙬", + "\\bisansx" : "𝙭", + "\\bisansy" : "𝙮", + "\\bisansz" : "𝙯", + "\\ttA" : "𝙰", + "\\ttB" : "𝙱", + "\\ttC" : "𝙲", + "\\ttD" : "𝙳", + "\\ttE" : "𝙴", + "\\ttF" : "𝙵", + "\\ttG" : "𝙶", + "\\ttH" : "𝙷", + "\\ttI" : "𝙸", + "\\ttJ" : "𝙹", + "\\ttK" : "𝙺", + "\\ttL" : "𝙻", + "\\ttM" : "𝙼", + "\\ttN" : "𝙽", + "\\ttO" : "𝙾", + "\\ttP" : "𝙿", + "\\ttQ" : "𝚀", + "\\ttR" : "𝚁", + "\\ttS" : "𝚂", + "\\ttT" : "𝚃", + "\\ttU" : "𝚄", + "\\ttV" : "𝚅", + "\\ttW" : "𝚆", + "\\ttX" : "𝚇", + "\\ttY" : "𝚈", + "\\ttZ" : "𝚉", + "\\tta" : "𝚊", + "\\ttb" : "𝚋", + "\\ttc" : "𝚌", + "\\ttd" : "𝚍", + "\\tte" : "𝚎", + "\\ttf" : "𝚏", + "\\ttg" : "𝚐", + "\\tth" : "𝚑", + "\\tti" : "𝚒", + "\\ttj" : "𝚓", + "\\ttk" : "𝚔", + "\\ttl" : "𝚕", + "\\ttm" : "𝚖", + "\\ttn" : "𝚗", + "\\tto" : "𝚘", + "\\ttp" : "𝚙", + "\\ttq" : "𝚚", + "\\ttr" : "𝚛", + "\\tts" : "𝚜", + "\\ttt" : "𝚝", + "\\ttu" : "𝚞", + "\\ttv" : "𝚟", + "\\ttw" : "𝚠", + "\\ttx" : "𝚡", + "\\tty" : "𝚢", + "\\ttz" : "𝚣", + "\\bfAlpha" : "𝚨", + "\\bfBeta" : "𝚩", + "\\bfGamma" : "𝚪", + "\\bfDelta" : "𝚫", + "\\bfEpsilon" : "𝚬", + "\\bfZeta" : "𝚭", + "\\bfEta" : "𝚮", + "\\bfTheta" : "𝚯", + "\\bfIota" : "𝚰", + "\\bfKappa" : "𝚱", + "\\bfLambda" : "𝚲", + "\\bfMu" : "𝚳", + "\\bfNu" : "𝚴", + "\\bfXi" : "𝚵", + "\\bfOmicron" : "𝚶", + "\\bfPi" : "𝚷", + "\\bfRho" : "𝚸", + "\\bfvarTheta" : "𝚹", + "\\bfSigma" : "𝚺", + "\\bfTau" : "𝚻", + "\\bfUpsilon" : "𝚼", + "\\bfPhi" : "𝚽", + "\\bfChi" : "𝚾", + "\\bfPsi" : "𝚿", + "\\bfOmega" : "𝛀", + "\\bfalpha" : "𝛂", + "\\bfbeta" : "𝛃", + "\\bfgamma" : "𝛄", + "\\bfdelta" : "𝛅", + "\\bfepsilon" : "𝛆", + "\\bfzeta" : "𝛇", + "\\bfeta" : "𝛈", + "\\bftheta" : "𝛉", + "\\bfiota" : "𝛊", + "\\bfkappa" : "𝛋", + "\\bflambda" : "𝛌", + "\\bfmu" : "𝛍", + "\\bfnu" : "𝛎", + "\\bfxi" : "𝛏", + "\\bfomicron" : "𝛐", + "\\bfpi" : "𝛑", + "\\bfrho" : "𝛒", + "\\bfvarsigma" : "𝛓", + "\\bfsigma" : "𝛔", + "\\bftau" : "𝛕", + "\\bfupsilon" : "𝛖", + "\\bfvarphi" : "𝛗", + "\\bfchi" : "𝛘", + "\\bfpsi" : "𝛙", + "\\bfomega" : "𝛚", + "\\bfvarepsilon" : "𝛜", + "\\bfvartheta" : "𝛝", + "\\bfvarkappa" : "𝛞", + "\\bfphi" : "𝛟", + "\\bfvarrho" : "𝛠", + "\\bfvarpi" : "𝛡", + "\\itAlpha" : "𝛢", + "\\itBeta" : "𝛣", + "\\itGamma" : "𝛤", + "\\itDelta" : "𝛥", + "\\itEpsilon" : "𝛦", + "\\itZeta" : "𝛧", + "\\itEta" : "𝛨", + "\\itTheta" : "𝛩", + "\\itIota" : "𝛪", + "\\itKappa" : "𝛫", + "\\itLambda" : "𝛬", + "\\itMu" : "𝛭", + "\\itNu" : "𝛮", + "\\itXi" : "𝛯", + "\\itOmicron" : "𝛰", + "\\itPi" : "𝛱", + "\\itRho" : "𝛲", + "\\itvarTheta" : "𝛳", + "\\itSigma" : "𝛴", + "\\itTau" : "𝛵", + "\\itUpsilon" : "𝛶", + "\\itPhi" : "𝛷", + "\\itChi" : "𝛸", + "\\itPsi" : "𝛹", + "\\itOmega" : "𝛺", + "\\italpha" : "𝛼", + "\\itbeta" : "𝛽", + "\\itgamma" : "𝛾", + "\\itdelta" : "𝛿", + "\\itepsilon" : "𝜀", + "\\itzeta" : "𝜁", + "\\iteta" : "𝜂", + "\\ittheta" : "𝜃", + "\\itiota" : "𝜄", + "\\itkappa" : "𝜅", + "\\itlambda" : "𝜆", + "\\itmu" : "𝜇", + "\\itnu" : "𝜈", + "\\itxi" : "𝜉", + "\\itomicron" : "𝜊", + "\\itpi" : "𝜋", + "\\itrho" : "𝜌", + "\\itvarsigma" : "𝜍", + "\\itsigma" : "𝜎", + "\\ittau" : "𝜏", + "\\itupsilon" : "𝜐", + "\\itphi" : "𝜑", + "\\itchi" : "𝜒", + "\\itpsi" : "𝜓", + "\\itomega" : "𝜔", + "\\itvarepsilon" : "𝜖", + "\\itvartheta" : "𝜗", + "\\itvarkappa" : "𝜘", + "\\itvarphi" : "𝜙", + "\\itvarrho" : "𝜚", + "\\itvarpi" : "𝜛", + "\\biAlpha" : "𝜜", + "\\biBeta" : "𝜝", + "\\biGamma" : "𝜞", + "\\biDelta" : "𝜟", + "\\biEpsilon" : "𝜠", + "\\biZeta" : "𝜡", + "\\biEta" : "𝜢", + "\\biTheta" : "𝜣", + "\\biIota" : "𝜤", + "\\biKappa" : "𝜥", + "\\biLambda" : "𝜦", + "\\biMu" : "𝜧", + "\\biNu" : "𝜨", + "\\biXi" : "𝜩", + "\\biOmicron" : "𝜪", + "\\biPi" : "𝜫", + "\\biRho" : "𝜬", + "\\bivarTheta" : "𝜭", + "\\biSigma" : "𝜮", + "\\biTau" : "𝜯", + "\\biUpsilon" : "𝜰", + "\\biPhi" : "𝜱", + "\\biChi" : "𝜲", + "\\biPsi" : "𝜳", + "\\biOmega" : "𝜴", + "\\bialpha" : "𝜶", + "\\bibeta" : "𝜷", + "\\bigamma" : "𝜸", + "\\bidelta" : "𝜹", + "\\biepsilon" : "𝜺", + "\\bizeta" : "𝜻", + "\\bieta" : "𝜼", + "\\bitheta" : "𝜽", + "\\biiota" : "𝜾", + "\\bikappa" : "𝜿", + "\\bilambda" : "𝝀", + "\\bimu" : "𝝁", + "\\binu" : "𝝂", + "\\bixi" : "𝝃", + "\\biomicron" : "𝝄", + "\\bipi" : "𝝅", + "\\birho" : "𝝆", + "\\bivarsigma" : "𝝇", + "\\bisigma" : "𝝈", + "\\bitau" : "𝝉", + "\\biupsilon" : "𝝊", + "\\biphi" : "𝝋", + "\\bichi" : "𝝌", + "\\bipsi" : "𝝍", + "\\biomega" : "𝝎", + "\\bivarepsilon" : "𝝐", + "\\bivartheta" : "𝝑", + "\\bivarkappa" : "𝝒", + "\\bivarphi" : "𝝓", + "\\bivarrho" : "𝝔", + "\\bivarpi" : "𝝕", + "\\bsansAlpha" : "𝝖", + "\\bsansBeta" : "𝝗", + "\\bsansGamma" : "𝝘", + "\\bsansDelta" : "𝝙", + "\\bsansEpsilon" : "𝝚", + "\\bsansZeta" : "𝝛", + "\\bsansEta" : "𝝜", + "\\bsansTheta" : "𝝝", + "\\bsansIota" : "𝝞", + "\\bsansKappa" : "𝝟", + "\\bsansLambda" : "𝝠", + "\\bsansMu" : "𝝡", + "\\bsansNu" : "𝝢", + "\\bsansXi" : "𝝣", + "\\bsansOmicron" : "𝝤", + "\\bsansPi" : "𝝥", + "\\bsansRho" : "𝝦", + "\\bsansvarTheta" : "𝝧", + "\\bsansSigma" : "𝝨", + "\\bsansTau" : "𝝩", + "\\bsansUpsilon" : "𝝪", + "\\bsansPhi" : "𝝫", + "\\bsansChi" : "𝝬", + "\\bsansPsi" : "𝝭", + "\\bsansOmega" : "𝝮", + "\\bsansalpha" : "𝝰", + "\\bsansbeta" : "𝝱", + "\\bsansgamma" : "𝝲", + "\\bsansdelta" : "𝝳", + "\\bsansepsilon" : "𝝴", + "\\bsanszeta" : "𝝵", + "\\bsanseta" : "𝝶", + "\\bsanstheta" : "𝝷", + "\\bsansiota" : "𝝸", + "\\bsanskappa" : "𝝹", + "\\bsanslambda" : "𝝺", + "\\bsansmu" : "𝝻", + "\\bsansnu" : "𝝼", + "\\bsansxi" : "𝝽", + "\\bsansomicron" : "𝝾", + "\\bsanspi" : "𝝿", + "\\bsansrho" : "𝞀", + "\\bsansvarsigma" : "𝞁", + "\\bsanssigma" : "𝞂", + "\\bsanstau" : "𝞃", + "\\bsansupsilon" : "𝞄", + "\\bsansphi" : "𝞅", + "\\bsanschi" : "𝞆", + "\\bsanspsi" : "𝞇", + "\\bsansomega" : "𝞈", + "\\bsansvarepsilon" : "𝞊", + "\\bsansvartheta" : "𝞋", + "\\bsansvarkappa" : "𝞌", + "\\bsansvarphi" : "𝞍", + "\\bsansvarrho" : "𝞎", + "\\bsansvarpi" : "𝞏", + "\\bisansAlpha" : "𝞐", + "\\bisansBeta" : "𝞑", + "\\bisansGamma" : "𝞒", + "\\bisansDelta" : "𝞓", + "\\bisansEpsilon" : "𝞔", + "\\bisansZeta" : "𝞕", + "\\bisansEta" : "𝞖", + "\\bisansTheta" : "𝞗", + "\\bisansIota" : "𝞘", + "\\bisansKappa" : "𝞙", + "\\bisansLambda" : "𝞚", + "\\bisansMu" : "𝞛", + "\\bisansNu" : "𝞜", + "\\bisansXi" : "𝞝", + "\\bisansOmicron" : "𝞞", + "\\bisansPi" : "𝞟", + "\\bisansRho" : "𝞠", + "\\bisansvarTheta" : "𝞡", + "\\bisansSigma" : "𝞢", + "\\bisansTau" : "𝞣", + "\\bisansUpsilon" : "𝞤", + "\\bisansPhi" : "𝞥", + "\\bisansChi" : "𝞦", + "\\bisansPsi" : "𝞧", + "\\bisansOmega" : "𝞨", + "\\bisansalpha" : "𝞪", + "\\bisansbeta" : "𝞫", + "\\bisansgamma" : "𝞬", + "\\bisansdelta" : "𝞭", + "\\bisansepsilon" : "𝞮", + "\\bisanszeta" : "𝞯", + "\\bisanseta" : "𝞰", + "\\bisanstheta" : "𝞱", + "\\bisansiota" : "𝞲", + "\\bisanskappa" : "𝞳", + "\\bisanslambda" : "𝞴", + "\\bisansmu" : "𝞵", + "\\bisansnu" : "𝞶", + "\\bisansxi" : "𝞷", + "\\bisansomicron" : "𝞸", + "\\bisanspi" : "𝞹", + "\\bisansrho" : "𝞺", + "\\bisansvarsigma" : "𝞻", + "\\bisanssigma" : "𝞼", + "\\bisanstau" : "𝞽", + "\\bisansupsilon" : "𝞾", + "\\bisansphi" : "𝞿", + "\\bisanschi" : "𝟀", + "\\bisanspsi" : "𝟁", + "\\bisansomega" : "𝟂", + "\\bisansvarepsilon" : "𝟄", + "\\bisansvartheta" : "𝟅", + "\\bisansvarkappa" : "𝟆", + "\\bisansvarphi" : "𝟇", + "\\bisansvarrho" : "𝟈", + "\\bisansvarpi" : "𝟉", + "\\bfzero" : "𝟎", + "\\bfone" : "𝟏", + "\\bftwo" : "𝟐", + "\\bfthree" : "𝟑", + "\\bffour" : "𝟒", + "\\bffive" : "𝟓", + "\\bfsix" : "𝟔", + "\\bfseven" : "𝟕", + "\\bfeight" : "𝟖", + "\\bfnine" : "𝟗", + "\\bbzero" : "𝟘", + "\\bbone" : "𝟙", + "\\bbtwo" : "𝟚", + "\\bbthree" : "𝟛", + "\\bbfour" : "𝟜", + "\\bbfive" : "𝟝", + "\\bbsix" : "𝟞", + "\\bbseven" : "𝟟", + "\\bbeight" : "𝟠", + "\\bbnine" : "𝟡", + "\\sanszero" : "𝟢", + "\\sansone" : "𝟣", + "\\sanstwo" : "𝟤", + "\\sansthree" : "𝟥", + "\\sansfour" : "𝟦", + "\\sansfive" : "𝟧", + "\\sanssix" : "𝟨", + "\\sansseven" : "𝟩", + "\\sanseight" : "𝟪", + "\\sansnine" : "𝟫", + "\\bsanszero" : "𝟬", + "\\bsansone" : "𝟭", + "\\bsanstwo" : "𝟮", + "\\bsansthree" : "𝟯", + "\\bsansfour" : "𝟰", + "\\bsansfive" : "𝟱", + "\\bsanssix" : "𝟲", + "\\bsansseven" : "𝟳", + "\\bsanseight" : "𝟴", + "\\bsansnine" : "𝟵", + "\\ttzero" : "𝟶", + "\\ttone" : "𝟷", + "\\tttwo" : "𝟸", + "\\ttthree" : "𝟹", + "\\ttfour" : "𝟺", + "\\ttfive" : "𝟻", + "\\ttsix" : "𝟼", + "\\ttseven" : "𝟽", + "\\tteight" : "𝟾", + "\\ttnine" : "𝟿", + "\\underbar" : "̲", + "\\underleftrightarrow" : "͍", } diff --git a/tools/gen_latex_symbols.py b/tools/gen_latex_symbols.py index 7eb684425d9..038d8ea2e5a 100644 --- a/tools/gen_latex_symbols.py +++ b/tools/gen_latex_symbols.py @@ -18,28 +18,33 @@ # Import the Julia LaTeX symbols print('Importing latex_symbols.js from Julia...') import requests -url = 'https://raw.githubusercontent.com/JuliaLang/julia/master/base/latex_symbols.jl' +url = 'https://raw.githubusercontent.com/JuliaLang/julia/master/stdlib/REPL/src/latex_symbols.jl' r = requests.get(url) # Build a list of key, value pairs print('Building a list of (latex, unicode) key-value pairs...') -lines = r.text.splitlines()[60:] -lines = [line for line in lines if '=>' in line] -lines = [line.replace('=>',':') for line in lines] - -def line_to_tuple(line): - """Convert a single line of the .jl file to a 2-tuple of strings like ("\\alpha", "α")""" - kv = line.split(',')[0].split(':') -# kv = tuple(line.strip(', ').split(':')) - k, v = kv[0].strip(' "'), kv[1].strip(' "') -# if not test_ident(v): -# print(line) - return k, v - -assert line_to_tuple(' "\\sqrt" : "\u221A",') == ('\\sqrt', '\u221A') -lines = [line_to_tuple(line) for line in lines] - +lines = r.text.splitlines() + +prefixes_line = lines.index('# "font" prefixes') +symbols_line = lines.index('# manual additions:') + +prefix_dict = {} +for l in lines[prefixes_line + 1: symbols_line]: + p = l.split() + if not p or p[1] == 'latex_symbols': continue + prefix_dict[p[1]] = p[3] + +idents = [] +for l in lines[symbols_line:]: + if not '=>' in l: continue # if it's not a def, skip + if '#' in l: l = l[:l.index('#')] # get rid of eol comments + x, y = l.strip().split('=>') + if '*' in x: # if a prefix is present substitute it with its value + p, x = x.split('*') + x = prefix_dict[p][:-1] + x[1:] + x, y = x.split('"')[1], y.split('"')[1] # get the values in quotes + idents.append((x, y)) # Filter out non-valid identifiers print('Filtering out characters that are not valid Python 3 identifiers') @@ -53,8 +58,7 @@ def test_ident(i): assert test_ident("α") assert not test_ident('‴') -valid_idents = [line for line in lines if test_ident(line[1])] - +valid_idents = [line for line in idents if test_ident(line[1])] # Write the `latex_symbols.py` module in the cwd From 6830e3d44cb82733ed32777743e033dd2faa487c Mon Sep 17 00:00:00 2001 From: luciana Date: Mon, 15 Oct 2018 19:58:21 -0300 Subject: [PATCH 0377/3726] added skipif for sqlite3 version > 3.24.0 --- IPython/core/tests/test_history.py | 39 +++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index fcc22f1c00a..c3a2007c739 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -1,9 +1,9 @@ # coding: utf-8 """Tests for the IPython tab-completion machinery. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Module imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # stdlib import io @@ -11,6 +11,7 @@ import sys import tempfile from datetime import datetime +import sqlite3 # third party import nose.tools as nt @@ -20,9 +21,14 @@ from IPython.utils.tempdir import TemporaryDirectory from IPython.core.history import HistoryManager, extract_hist_ranges +from testing.decorators import skipif + + def setUp(): nt.assert_equal(sys.getdefaultencoding(), "utf-8") + +@skipif(sqlite3.version_info > (3, 24, 0)) def test_history(): ip = get_ipython() with TemporaryDirectory() as tmpdir: @@ -40,17 +46,17 @@ def test_history(): ip.history_manager.store_output(3) nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) - + # Detailed tests for _get_range_session grs = ip.history_manager._get_range_session - nt.assert_equal(list(grs(start=2,stop=-1)), list(zip([0], [2], hist[1:-1]))) - nt.assert_equal(list(grs(start=-2)), list(zip([0,0], [2,3], hist[-2:]))) - nt.assert_equal(list(grs(output=True)), list(zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))) + nt.assert_equal(list(grs(start=2, stop=-1)), list(zip([0], [2], hist[1:-1]))) + nt.assert_equal(list(grs(start=-2)), list(zip([0, 0], [2, 3], hist[-2:]))) + nt.assert_equal(list(grs(output=True)), list(zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, 'spam'])))) # Check whether specifying a range beyond the end of the current # session results in an error (gh-804) ip.magic('%hist 2-500') - + # Check that we can write non-ascii characters to a file ip.magic("%%hist -f %s" % os.path.join(tmpdir, "test1")) ip.magic("%%hist -pf %s" % os.path.join(tmpdir, "test2")) @@ -66,18 +72,18 @@ def test_history(): for i, cmd in enumerate(newcmds, start=1): ip.history_manager.store_inputs(i, cmd) gothist = ip.history_manager.get_range(start=1, stop=4) - nt.assert_equal(list(gothist), list(zip([0,0,0],[1,2,3], newcmds))) + nt.assert_equal(list(gothist), list(zip([0, 0, 0], [1, 2, 3], newcmds))) # Previous session: gothist = ip.history_manager.get_range(-1, 1, 4) - nt.assert_equal(list(gothist), list(zip([1,1,1],[1,2,3], hist))) + nt.assert_equal(list(gothist), list(zip([1, 1, 1], [1, 2, 3], hist))) newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)] # Check get_hist_tail gothist = ip.history_manager.get_tail(5, output=True, - include_latest=True) + include_latest=True) expected = [(1, 3, (hist[-1], "spam"))] \ - + [(s, n, (c, None)) for (s, n, c) in newhist] + + [(s, n, (c, None)) for (s, n, c) in newhist] nt.assert_equal(list(gothist), expected) gothist = ip.history_manager.get_tail(2) @@ -85,8 +91,9 @@ def test_history(): nt.assert_equal(list(gothist), expected) # Check get_hist_search + gothist = ip.history_manager.search("*test*") - nt.assert_equal(list(gothist), [(1,2,hist[1])] ) + nt.assert_equal(list(gothist), [(1, 2, hist[1])]) gothist = ip.history_manager.search("*=*") nt.assert_equal(list(gothist), @@ -119,14 +126,14 @@ def test_history(): newhist[3]]) gothist = ip.history_manager.search("b*", output=True) - nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) + nt.assert_equal(list(gothist), [(1, 3, (hist[2], "spam"))]) # Cross testing: check that magic %save can get previous session. testfilename = os.path.realpath(os.path.join(tmpdir, "test.py")) ip.magic("save " + testfilename + " ~1/1-3") with io.open(testfilename, encoding='utf-8') as testfile: nt.assert_equal(testfile.read(), - u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") + u"# coding: utf-8\n" + u"\n".join(hist) + u"\n") # Duplicate line numbers - check that it doesn't crash, and # gets a new session @@ -155,6 +162,7 @@ def test_extract_hist_ranges(): actual = list(extract_hist_ranges(instr)) nt.assert_equal(actual, expected) + def test_magic_rerun(): """Simple test for %rerun (no args -> rerun last line)""" ip = get_ipython() @@ -164,11 +172,13 @@ def test_magic_rerun(): ip.run_cell("%rerun", store_history=True) nt.assert_equal(ip.user_ns["a"], 12) + def test_timestamp_type(): ip = get_ipython() info = ip.history_manager.get_session_info() nt.assert_true(isinstance(info[1], datetime)) + def test_hist_file_config(): cfg = Config() tfile = tempfile.NamedTemporaryFile(delete=False) @@ -185,6 +195,7 @@ def test_hist_file_config(): # delete it. I have no clue why pass + def test_histmanager_disabled(): """Ensure that disabling the history manager doesn't create a database.""" cfg = Config() From 6acfa6fe9b76d0f2c133da438b1f9a1cc6df3a38 Mon Sep 17 00:00:00 2001 From: luciana Date: Mon, 15 Oct 2018 20:19:52 -0300 Subject: [PATCH 0378/3726] added skipif for sqlite3 version > 3.24.0 --- IPython/core/tests/test_history.py | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index c3a2007c739..a6815edd131 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -1,9 +1,9 @@ # coding: utf-8 """Tests for the IPython tab-completion machinery. """ -# ----------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Module imports -# ----------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # stdlib import io @@ -20,15 +20,12 @@ from traitlets.config.loader import Config from IPython.utils.tempdir import TemporaryDirectory from IPython.core.history import HistoryManager, extract_hist_ranges - -from testing.decorators import skipif - +from IPython.testing.decorators import skipif def setUp(): nt.assert_equal(sys.getdefaultencoding(), "utf-8") - -@skipif(sqlite3.version_info > (3, 24, 0)) +@skipif(sqlite3.version_info > (3,24,0)) def test_history(): ip = get_ipython() with TemporaryDirectory() as tmpdir: @@ -49,9 +46,9 @@ def test_history(): # Detailed tests for _get_range_session grs = ip.history_manager._get_range_session - nt.assert_equal(list(grs(start=2, stop=-1)), list(zip([0], [2], hist[1:-1]))) - nt.assert_equal(list(grs(start=-2)), list(zip([0, 0], [2, 3], hist[-2:]))) - nt.assert_equal(list(grs(output=True)), list(zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, 'spam'])))) + nt.assert_equal(list(grs(start=2,stop=-1)), list(zip([0], [2], hist[1:-1]))) + nt.assert_equal(list(grs(start=-2)), list(zip([0,0], [2,3], hist[-2:]))) + nt.assert_equal(list(grs(output=True)), list(zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))) # Check whether specifying a range beyond the end of the current # session results in an error (gh-804) @@ -72,18 +69,18 @@ def test_history(): for i, cmd in enumerate(newcmds, start=1): ip.history_manager.store_inputs(i, cmd) gothist = ip.history_manager.get_range(start=1, stop=4) - nt.assert_equal(list(gothist), list(zip([0, 0, 0], [1, 2, 3], newcmds))) + nt.assert_equal(list(gothist), list(zip([0,0,0],[1,2,3], newcmds))) # Previous session: gothist = ip.history_manager.get_range(-1, 1, 4) - nt.assert_equal(list(gothist), list(zip([1, 1, 1], [1, 2, 3], hist))) + nt.assert_equal(list(gothist), list(zip([1,1,1],[1,2,3], hist))) newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)] # Check get_hist_tail gothist = ip.history_manager.get_tail(5, output=True, - include_latest=True) + include_latest=True) expected = [(1, 3, (hist[-1], "spam"))] \ - + [(s, n, (c, None)) for (s, n, c) in newhist] + + [(s, n, (c, None)) for (s, n, c) in newhist] nt.assert_equal(list(gothist), expected) gothist = ip.history_manager.get_tail(2) @@ -93,7 +90,7 @@ def test_history(): # Check get_hist_search gothist = ip.history_manager.search("*test*") - nt.assert_equal(list(gothist), [(1, 2, hist[1])]) + nt.assert_equal(list(gothist), [(1,2,hist[1])] ) gothist = ip.history_manager.search("*=*") nt.assert_equal(list(gothist), @@ -126,14 +123,14 @@ def test_history(): newhist[3]]) gothist = ip.history_manager.search("b*", output=True) - nt.assert_equal(list(gothist), [(1, 3, (hist[2], "spam"))]) + nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) # Cross testing: check that magic %save can get previous session. testfilename = os.path.realpath(os.path.join(tmpdir, "test.py")) ip.magic("save " + testfilename + " ~1/1-3") with io.open(testfilename, encoding='utf-8') as testfile: nt.assert_equal(testfile.read(), - u"# coding: utf-8\n" + u"\n".join(hist) + u"\n") + u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") # Duplicate line numbers - check that it doesn't crash, and # gets a new session @@ -162,7 +159,6 @@ def test_extract_hist_ranges(): actual = list(extract_hist_ranges(instr)) nt.assert_equal(actual, expected) - def test_magic_rerun(): """Simple test for %rerun (no args -> rerun last line)""" ip = get_ipython() @@ -172,13 +168,11 @@ def test_magic_rerun(): ip.run_cell("%rerun", store_history=True) nt.assert_equal(ip.user_ns["a"], 12) - def test_timestamp_type(): ip = get_ipython() info = ip.history_manager.get_session_info() nt.assert_true(isinstance(info[1], datetime)) - def test_hist_file_config(): cfg = Config() tfile = tempfile.NamedTemporaryFile(delete=False) @@ -195,7 +189,6 @@ def test_hist_file_config(): # delete it. I have no clue why pass - def test_histmanager_disabled(): """Ensure that disabling the history manager doesn't create a database.""" cfg = Config() From 8cd973c7c37e3571d06748e323b724ed5292d6fe Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Mon, 8 Oct 2018 19:31:16 +0500 Subject: [PATCH 0379/3726] Replace depricated time.clock with time.perf_counter issue#11375 --- IPython/utils/timing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 3d4d9f8d9bc..6bb176f338c 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -59,12 +59,12 @@ def clock2(): except ImportError: # There is no distinction of user/system time under windows, so we just use # time.clock() for everything... - clocku = clocks = clock = time.clock + clocku = clocks = clock = time.perf_counter def clock2(): """Under windows, system CPU time can't be measured. This just returns clock() and zero.""" - return time.clock(),0.0 + return time.perf_counter(),0.0 def timings_out(reps,func,*args,**kw): From 375d066bd09171dba6839a19dc3c12d92192f8af Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Mon, 8 Oct 2018 22:23:52 +0500 Subject: [PATCH 0380/3726] Update comments issue#11375 --- IPython/utils/timing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 6bb176f338c..9a31affadf0 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -58,12 +58,12 @@ def clock2(): return resource.getrusage(resource.RUSAGE_SELF)[:2] except ImportError: # There is no distinction of user/system time under windows, so we just use - # time.clock() for everything... + # time.perff_counter() for everything... clocku = clocks = clock = time.perf_counter def clock2(): """Under windows, system CPU time can't be measured. - This just returns clock() and zero.""" + This just returns perf_counter() and zero.""" return time.perf_counter(),0.0 From 7603d7fac72d37089f73c59048417fab9268281d Mon Sep 17 00:00:00 2001 From: ammarmallik Date: Thu, 11 Oct 2018 18:29:18 +0500 Subject: [PATCH 0381/3726] Replace deprecated time.time() with time.perf_counter() issue#11375 --- IPython/core/magics/execution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index d04ace80c10..152950ea1d5 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -915,7 +915,7 @@ def _run_with_timing(run, nruns): Number of times to execute `run`. """ - twall0 = time.time() + twall0 = time.perf_counter() if nruns == 1: t0 = clock2() run() @@ -938,7 +938,7 @@ def _run_with_timing(run, nruns): print(" Times : %10s %10s" % ('Total', 'Per run')) print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) - twall1 = time.time() + twall1 = time.perf_counter() print("Wall time: %10.2f s." % (twall1 - twall0)) @skip_doctest From 952e3ad6e35b44fbbe24e128fc8949c93c51ba45 Mon Sep 17 00:00:00 2001 From: Shao Yang Date: Fri, 12 Oct 2018 23:19:38 +0800 Subject: [PATCH 0382/3726] change magics from %%file to %%writefile --- IPython/core/tests/test_magic.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index dfeabca1f21..b9c1533905b 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -738,11 +738,11 @@ def cellm33(self, line, cell): nt.assert_equal(c33, None) def test_file(): - """Basic %%file""" + """Basic %%writefile""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ 'line1', 'line2', ])) @@ -752,12 +752,12 @@ def test_file(): nt.assert_in('line2', s) def test_file_var_expand(): - """%%file $filename""" + """%%writefile $filename""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') ip.user_ns['filename'] = fname - ip.run_cell_magic("file", '$filename', u'\n'.join([ + ip.run_cell_magic("writefile", '$filename', u'\n'.join([ 'line1', 'line2', ])) @@ -767,11 +767,11 @@ def test_file_var_expand(): nt.assert_in('line2', s) def test_file_unicode(): - """%%file with unicode cell""" + """%%writefile with unicode cell""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file1') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ u'liné1', u'liné2', ])) @@ -781,15 +781,15 @@ def test_file_unicode(): nt.assert_in(u'liné2', s) def test_file_amend(): - """%%file -a amends files""" + """%%writefile -a amends files""" ip = get_ipython() with TemporaryDirectory() as td: fname = os.path.join(td, 'file2') - ip.run_cell_magic("file", fname, u'\n'.join([ + ip.run_cell_magic("writefile", fname, u'\n'.join([ 'line1', 'line2', ])) - ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ + ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([ 'line3', 'line4', ])) From 73f493f520bb79e2f7e693d8116849d311177e3f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 15 Oct 2018 18:04:42 -0700 Subject: [PATCH 0383/3726] Restore some functionality of the sphinx directive. See #11362 The issue is in 2 part, before IPython 7.0 the input splitter was state full, this was (in part) due to readline. The second part is that because of this, we had to be a bit adressive of what was considered complete code (it had to have 2 new line). This is now not required anymore as we can submit stuff as a whole. I hope that this fixes that. I have another fix in mind that count (and reset) the number of consecutive blank line, but that will be more complicated end code. --- IPython/sphinxext/ipython_directive.py | 47 +++++++++++--------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index 6fcf9d848df..70e2f5bd631 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -201,7 +201,6 @@ from IPython import InteractiveShell from IPython.core.profiledir import ProfileDir - use_matpltolib = False try: import matplotlib @@ -356,7 +355,6 @@ def __init__(self, exec_lines=None): self.user_ns = self.IP.user_ns self.user_global_ns = self.IP.user_global_ns - self.lines_waiting = [] self.input = '' self.output = '' self.tmp_profile_dir = tmp_profile_dir @@ -387,16 +385,16 @@ def clear_cout(self): self.cout.seek(0) self.cout.truncate(0) - def process_input_line(self, line, store_history=True): + def process_input_line(self, line, store_history): + return self.process_input_lines([line], store_history=store_history) + + def process_input_lines(self, lines, store_history=True): """process the input, capturing stdout""" stdout = sys.stdout + source_raw = '\n'.join(lines) try: sys.stdout = self.cout - self.lines_waiting.append(line) - source_raw = ''.join(self.lines_waiting) - if self.IP.check_complete(source_raw)[0] != 'incomplete': - self.lines_waiting = [] - self.IP.run_cell(source_raw, store_history=store_history) + self.IP.run_cell(source_raw, store_history=store_history) finally: sys.stdout = stdout @@ -470,28 +468,25 @@ def process_input(self, data, input_prompt, lineno): # Note: catch_warnings is not thread safe with warnings.catch_warnings(record=True) as ws: - for i, line in enumerate(input_lines): - if line.endswith(';'): - is_semicolon = True + if input_lines[0].endswith(';'): + is_semicolon = True + #for i, line in enumerate(input_lines): + + # process the first input line + if is_verbatim: + self.process_input_lines(['']) + self.IP.execution_count += 1 # increment it anyway + else: + # only submit the line in non-verbatim mode + self.process_input_lines(input_lines, store_history=store_history) + if not is_suppress: + for i, line in enumerate(input_lines): if i == 0: - # process the first input line - if is_verbatim: - self.process_input_line('') - self.IP.execution_count += 1 # increment it anyway - else: - # only submit the line in non-verbatim mode - self.process_input_line(line, store_history=store_history) formatted_line = '%s %s'%(input_prompt, line) else: - # process a continuation line - if not is_verbatim: - self.process_input_line(line, store_history=store_history) - formatted_line = '%s %s'%(continuation, line) - - if not is_suppress: - ret.append(formatted_line) + ret.append(formatted_line) if not is_suppress and len(rest.strip()) and is_verbatim: # The "rest" is the standard output of the input. This needs to be @@ -582,7 +577,6 @@ def process_input(self, data, input_prompt, lineno): raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno)) self.cout.truncate(0) - return (ret, input_lines, processed_output, is_doctest, decorator, image_file, image_directive) @@ -734,7 +728,6 @@ def process_block(self, block): # will truncate tracebacks. sys.stdout.write(e) raise RuntimeError('An invalid block was detected.') - out_data = \ self.process_output(data, output_prompt, input_lines, output, is_doctest, decorator, From d12f0c8fdbbdf63ef8299af9f459df956d045815 Mon Sep 17 00:00:00 2001 From: Massimo Santini Date: Tue, 16 Oct 2018 08:45:18 +0200 Subject: [PATCH 0384/3726] Skipped a test about \\jmath that now is completed --- IPython/core/tests/test_completer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 56428bad2c8..4f90c9b7244 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -182,6 +182,7 @@ def test_forward_unicode_completion(): nt.assert_equal(len(matches), 1) nt.assert_equal(matches[0], 'Ⅴ') +@nt.nottest # now we have a completion for \jmath @dec.knownfailureif(sys.platform == 'win32', 'Fails if there is a C:\\j... path') def test_no_ascii_back_completion(): ip = get_ipython() From 11fc3682b4db84211b90673157c0a8f2b77c7e3f Mon Sep 17 00:00:00 2001 From: luciana Date: Tue, 16 Oct 2018 11:15:47 -0300 Subject: [PATCH 0385/3726] changed syntax --- IPython/core/tests/test_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index a6815edd131..1760e4681f9 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -25,7 +25,7 @@ def setUp(): nt.assert_equal(sys.getdefaultencoding(), "utf-8") -@skipif(sqlite3.version_info > (3,24,0)) +@skipif(sqlite3.sqlite_version_info > (3,24,0)) def test_history(): ip = get_ipython() with TemporaryDirectory() as tmpdir: From ac5788009da4123f0fa38c49c4de4e2ff1348e35 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 15 Oct 2018 20:32:22 -0700 Subject: [PATCH 0386/3726] Add an option (`ipython_warning_is_error`) to not stop on error. The behavior pre-6.5 was to keep on going even if unexpected exceptions or warnings were shown, on 7.x the default is to abort the build. For compat reasons (and convenience), you can now set back the behavior to the original one to just log to stderr and move on. --- IPython/sphinxext/ipython_directive.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index 70e2f5bd631..1e4f0d85610 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -84,6 +84,10 @@ The compiled regular expression to denote the start of IPython input lines. The default is ``re.compile('In \[(\d+)\]:\s?(.*)\s*')``. You shouldn't need to change this. +ipython_warning_is_error: [default to True] + Fail the build if something unexpected happen, for example if a block raise + an exception but does not have the `:okexcept:` flag. The exact behavior of + what is considered strict, may change between the sphinx directive version. ipython_rgxout: The compiled regular expression to denote the start of IPython output lines. The default is ``re.compile('Out\[(\d+)\]:\s?(.*)\s*')``. You @@ -559,7 +563,8 @@ def process_input(self, data, input_prompt, lineno): sys.stdout.write(s) sys.stdout.write(processed_output) sys.stdout.write('<<<' + ('-' * 73) + '\n\n') - raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno)) + if self.warning_is_error: + raise RuntimeError('Non Expected exception in `{}` line {}'.format(filename, lineno)) # output any warning raised during execution to stdout # unless :okwarning: has been specified. @@ -574,7 +579,8 @@ def process_input(self, data, input_prompt, lineno): w.filename, w.lineno, w.line) sys.stdout.write(s) sys.stdout.write('<<<' + ('-' * 73) + '\n') - raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno)) + if self.shell.warning_is_error: + raise RuntimeError('Non Expected warning in `{}` line {}'.format(filename, lineno)) self.cout.truncate(0) return (ret, input_lines, processed_output, @@ -899,6 +905,7 @@ def get_config_options(self): # get regex and prompt stuff rgxin = config.ipython_rgxin rgxout = config.ipython_rgxout + warning_is_error= config.ipython_warning_is_error promptin = config.ipython_promptin promptout = config.ipython_promptout mplbackend = config.ipython_mplbackend @@ -906,12 +913,12 @@ def get_config_options(self): hold_count = config.ipython_holdcount return (savefig_dir, source_dir, rgxin, rgxout, - promptin, promptout, mplbackend, exec_lines, hold_count) + promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error) def setup(self): # Get configuration values. (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, - mplbackend, exec_lines, hold_count) = self.get_config_options() + mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options() try: os.makedirs(savefig_dir) @@ -951,6 +958,7 @@ def setup(self): self.shell.savefig_dir = savefig_dir self.shell.source_dir = source_dir self.shell.hold_count = hold_count + self.shell.warning_is_error = warning_is_error # setup bookmark for saving figures directory self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, @@ -1028,6 +1036,7 @@ def setup(app): app.add_directive('ipython', IPythonDirective) app.add_config_value('ipython_savefig_dir', 'savefig', 'env') + app.add_config_value('ipython_warning_is_error', True, 'env') app.add_config_value('ipython_rgxin', re.compile('In \[(\d+)\]:\s?(.*)\s*'), 'env') app.add_config_value('ipython_rgxout', From 8852ac6d44c37a78b6a4084f0d60f45a8ee65c79 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 16 Oct 2018 09:00:19 -0700 Subject: [PATCH 0387/3726] Catch Syntax error as exceptions, get doc from history --- IPython/sphinxext/ipython_directive.py | 2 +- docs/source/development/index.rst | 1 + docs/source/development/ipython_directive.rst | 435 ++++++++++++++++++ 3 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 docs/source/development/ipython_directive.rst diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index 1e4f0d85610..72d3d4f7a2e 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -556,7 +556,7 @@ def process_input(self, data, input_prompt, lineno): # output any exceptions raised during execution to stdout # unless :okexcept: has been specified. - if not is_okexcept and "Traceback" in processed_output: + if not is_okexcept and (("Traceback" in processed_output) or ("SyntaxError" in processed_output)): s = "\nException in %s at block ending on line %s\n" % (filename, lineno) s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n" sys.stdout.write('\n\n>>>' + ('-' * 73)) diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index a75cafdc178..a01673fa935 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -19,3 +19,4 @@ Developer's guide for third party tools and libraries lexer config inputhook_app + ipython_directive diff --git a/docs/source/development/ipython_directive.rst b/docs/source/development/ipython_directive.rst new file mode 100644 index 00000000000..7beaa503818 --- /dev/null +++ b/docs/source/development/ipython_directive.rst @@ -0,0 +1,435 @@ + +.. _ipython_directive: + +======================== +IPython Sphinx Directive +======================== + +.. note:: + + This has been salvadged from history, so information may be approximate or + duplicated. Fixes welcome. + +The ipython directive is a stateful ipython shell for embedding in +sphinx documents. It knows about standard ipython prompts, and +extracts the input and output lines. These prompts will be renumbered +starting at ``1``. The inputs will be fed to an embedded ipython +interpreter and the outputs from that interpreter will be inserted as +well. For example, code blocks like the following:: + + .. ipython:: + + In [136]: x = 2 + + In [137]: x**3 + Out[137]: 8 + +will be rendered as + +.. ipython:: + + In [136]: x = 2 + + In [137]: x**3 + Out[137]: 8 + +.. note:: + + This tutorial should be read side-by-side with the Sphinx source + for this document because otherwise you will see only the rendered + output and not the code that generated it. Excepting the example + above, we will not in general be showing the literal ReST in this + document that generates the rendered output. + + +The state from previous sessions is stored, and standard error is +trapped. At doc build time, ipython's output and std err will be +inserted, and prompts will be renumbered. So the prompt below should +be renumbered in the rendered docs, and pick up where the block above +left off. + +.. ipython:: + :verbatim: + + In [138]: z = x*3 # x is recalled from previous block + + In [139]: z + Out[139]: 6 + + In [142]: print z + --------> print(z) + 6 + + In [141]: q = z[) # this is a syntax error -- we trap ipy exceptions + ------------------------------------------------------------ + File "", line 1 + q = z[) # this is a syntax error -- we trap ipy exceptions + ^ + SyntaxError: invalid syntax + + +The embedded interpreter supports some limited markup. For example, +you can put comments in your ipython sessions, which are reported +verbatim. There are some handy "pseudo-decorators" that let you +doctest the output. The inputs are fed to an embedded ipython +session and the outputs from the ipython session are inserted into +your doc. If the output in your doc and in the ipython session don't +match on a doctest assertion, an error will be + + +.. ipython:: + + In [1]: x = 'hello world' + + # this will raise an error if the ipython output is different + @doctest + In [2]: x.upper() + Out[2]: 'HELLO WORLD' + + # some readline features cannot be supported, so we allow + # "verbatim" blocks, which are dumped in verbatim except prompts + # are continuously numbered + @verbatim + In [3]: x.st + x.startswith x.strip + + +Multi-line input is supported. + +.. ipython:: + :verbatim: + + In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ + .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' + + In [131]: print url.split('&') + --------> print(url.split('&')) + ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', + +You can do doctesting on multi-line output as well. Just be careful +when using non-deterministic inputs like random numbers in the ipython +directive, because your inputs are ruin through a live interpreter, so +if you are doctesting random output you will get an error. Here we +"seed" the random number generator for deterministic output, and we +suppress the seed line so it doesn't show up in the rendered output + +.. ipython:: + + In [133]: import numpy.random + + @suppress + In [134]: numpy.random.seed(2358) + + @doctest + In [135]: numpy.random.rand(10,2) + Out[135]: + array([[0.64524308, 0.59943846], + [0.47102322, 0.8715456 ], + [0.29370834, 0.74776844], + [0.99539577, 0.1313423 ], + [0.16250302, 0.21103583], + [0.81626524, 0.1312433 ], + [0.67338089, 0.72302393], + [0.7566368 , 0.07033696], + [0.22591016, 0.77731835], + [0.0072729 , 0.34273127]]) + + +Another demonstration of multi-line input and output + +.. ipython:: + :verbatim: + + In [106]: print x + --------> print(x) + jdh + + In [109]: for i in range(10): + .....: print i + .....: + .....: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + +Most of the "pseudo-decorators" can be used an options to ipython +mode. For example, to setup matplotlib pylab but suppress the output, +you can do. When using the matplotlib ``use`` directive, it should +occur before any import of pylab. This will not show up in the +rendered docs, but the commands will be executed in the embedded +interpreter and subsequent line numbers will be incremented to reflect +the inputs:: + + + .. ipython:: + :suppress: + + In [144]: from pylab import * + + In [145]: ion() + +.. ipython:: + :suppress: + + In [144]: from pylab import * + + In [145]: ion() + +Likewise, you can set ``:doctest:`` or ``:verbatim:`` to apply these +settings to the entire block. For example, + +.. ipython:: + :verbatim: + + In [9]: cd mpl/examples/ + /home/jdhunter/mpl/examples + + In [10]: pwd + Out[10]: '/home/jdhunter/mpl/examples' + + + In [14]: cd mpl/examples/ + mpl/examples/animation/ mpl/examples/misc/ + mpl/examples/api/ mpl/examples/mplot3d/ + mpl/examples/axes_grid/ mpl/examples/pylab_examples/ + mpl/examples/event_handling/ mpl/examples/widgets + + In [14]: cd mpl/examples/widgets/ + /home/msierig/mpl/examples/widgets + + In [15]: !wc * + 2 12 77 README.txt + 40 97 884 buttons.py + 26 90 712 check_buttons.py + 19 52 416 cursor.py + 180 404 4882 menu.py + 16 45 337 multicursor.py + 36 106 916 radio_buttons.py + 48 226 2082 rectangle_selector.py + 43 118 1063 slider_demo.py + 40 124 1088 span_selector.py + 450 1274 12457 total + +You can create one or more pyplot plots and insert them with the +``@savefig`` decorator. + +.. ipython:: + + @savefig plot_simple.png width=4in + In [151]: plot([1,2,3]); + + # use a semicolon to suppress the output + @savefig hist_simple.png width=4in + In [151]: hist(np.random.randn(10000), 100); + +In a subsequent session, we can update the current figure with some +text, and then resave + +.. ipython:: + + + In [151]: ylabel('number') + + In [152]: title('normal distribution') + + @savefig hist_with_text.png width=4in + In [153]: grid(True) + +You can also have function definitions included in the source. + +.. ipython:: + + In [3]: def square(x): + ...: """ + ...: An overcomplicated square function as an example. + ...: """ + ...: if x < 0: + ...: x = abs(x) + ...: y = x * x + ...: return y + ...: + +Then call it from a subsequent section. + +.. ipython:: + + In [4]: square(3) + Out [4]: 9 + + In [5]: square(-2) + Out [5]: 4 + + +Writing Pure Python Code +------------------------ + +Pure python code is supported by the optional argument `python`. In this pure +python syntax you do not include the output from the python interpreter. The +following markup:: + + .. ipython:: python + + foo = 'bar' + print(foo) + foo = 2 + foo**2 + +Renders as + +.. ipython:: python + + foo = 'bar' + print(foo) + foo = 2 + foo**2 + +We can even plot from python, using the savefig decorator, as well as, suppress +output with a semicolon + +.. ipython:: python + + @savefig plot_simple_python.png width=4in + plot([1,2,3]); + +Similarly, std err is inserted + +.. ipython:: python + :okexcept: + + foo = 'bar' + foo[) + +Comments are handled and state is preserved + +.. ipython:: python + + # comments are handled + print(foo) + +If you don't see the next code block then the options work. + +.. ipython:: python + :suppress: + + ioff() + ion() + +Multi-line input is handled. + +.. ipython:: python + + line = 'Multi\ + line &\ + support &\ + works' + print(line.split('&')) + +Functions definitions are correctly parsed + +.. ipython:: python + + def square(x): + """ + An overcomplicated square function as an example. + """ + if x < 0: + x = abs(x) + y = x * x + return y + +And persist across sessions + +.. ipython:: python + + print(square(3)) + print(square(-2)) + +Pretty much anything you can do with the ipython code, you can do with +with a simple python script. Obviously, though it doesn't make sense +to use the doctest option. + +Pseudo-Decorators +================= + +Here are the supported decorators, and any optional arguments they +take. Some of the decorators can be used as options to the entire +block (eg ``verbatim`` and ``suppress``), and some only apply to the +line just below them (eg ``savefig``). + +@suppress + + execute the ipython input block, but suppress the input and output + block from the rendered output. Also, can be applied to the entire + ``.. ipython`` block as a directive option with ``:suppress:``. + +@verbatim + + insert the input and output block in verbatim, but auto-increment + the line numbers. Internally, the interpreter will be fed an empty + string, so it is a no-op that keeps line numbering consistent. + Also, can be applied to the entire ``.. ipython`` block as a + directive option with ``:verbatim:``. + +@savefig OUTFILE [IMAGE_OPTIONS] + + save the figure to the static directory and insert it into the + document, possibly binding it into a minipage and/or putting + code/figure label/references to associate the code and the + figure. Takes args to pass to the image directive (*scale*, + *width*, etc can be kwargs); see `image options + `_ + for details. + +@doctest + + Compare the pasted in output in the ipython block with the output + generated at doc build time, and raise errors if they don't + match. Also, can be applied to the entire ``.. ipython`` block as a + directive option with ``:doctest:``. + +Configuration Options +===================== + +ipython_savefig_dir + + The directory in which to save the figures. This is relative to the + Sphinx source directory. The default is `html_static_path`. + +ipython_rgxin + + The compiled regular expression to denote the start of IPython input + lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You + shouldn't need to change this. + +ipython_rgxout + + The compiled regular expression to denote the start of IPython output + lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You + shouldn't need to change this. + + +ipython_promptin + + The string to represent the IPython input prompt in the generated ReST. + The default is 'In [%d]:'. This expects that the line numbers are used + in the prompt. + +ipython_promptout + + The string to represent the IPython prompt in the generated ReST. The + default is 'Out [%d]:'. This expects that the line numbers are used + in the prompt. + + +Automatically generated documentation +===================================== + +.. automodule:: IPython.sphinxext.ipython_directive From bdcb05ed18153efb37a843cebedd8cded79a1953 Mon Sep 17 00:00:00 2001 From: luciana Date: Mon, 15 Oct 2018 19:58:21 -0300 Subject: [PATCH 0388/3726] Added skipif for sqlite3 version > 3.24.0 --- IPython/core/tests/test_history.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index fcc22f1c00a..1760e4681f9 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -11,6 +11,7 @@ import sys import tempfile from datetime import datetime +import sqlite3 # third party import nose.tools as nt @@ -19,10 +20,12 @@ from traitlets.config.loader import Config from IPython.utils.tempdir import TemporaryDirectory from IPython.core.history import HistoryManager, extract_hist_ranges +from IPython.testing.decorators import skipif def setUp(): nt.assert_equal(sys.getdefaultencoding(), "utf-8") +@skipif(sqlite3.sqlite_version_info > (3,24,0)) def test_history(): ip = get_ipython() with TemporaryDirectory() as tmpdir: @@ -40,7 +43,7 @@ def test_history(): ip.history_manager.store_output(3) nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) - + # Detailed tests for _get_range_session grs = ip.history_manager._get_range_session nt.assert_equal(list(grs(start=2,stop=-1)), list(zip([0], [2], hist[1:-1]))) @@ -50,7 +53,7 @@ def test_history(): # Check whether specifying a range beyond the end of the current # session results in an error (gh-804) ip.magic('%hist 2-500') - + # Check that we can write non-ascii characters to a file ip.magic("%%hist -f %s" % os.path.join(tmpdir, "test1")) ip.magic("%%hist -pf %s" % os.path.join(tmpdir, "test2")) @@ -85,6 +88,7 @@ def test_history(): nt.assert_equal(list(gothist), expected) # Check get_hist_search + gothist = ip.history_manager.search("*test*") nt.assert_equal(list(gothist), [(1,2,hist[1])] ) From 8c9ec3ae61e0b08c0fb2daa3f64007c4321ef3df Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 17 Oct 2018 17:15:29 -0700 Subject: [PATCH 0389/3726] Fix starting IPython in vi editing mode closes #11404 --- IPython/terminal/interactiveshell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 77ef445169b..e9f16494354 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -147,7 +147,8 @@ def _validate_editing_mode(self, proposal): @observe('editing_mode') def _editing_mode(self, change): u_mode = change.new.upper() - self.pt_app.editing_mode = u_mode + if self.pt_app: + self.pt_app.editing_mode = u_mode @observe('highlighting_style') @observe('colors') From f4ce5c669b10f7396fc8026e4869b5a552c5bbb6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 17 Oct 2018 17:25:25 -0700 Subject: [PATCH 0390/3726] fix minimal issues --- docs/source/development/ipython_directive.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/development/ipython_directive.rst b/docs/source/development/ipython_directive.rst index 7beaa503818..f3f262d4b5f 100644 --- a/docs/source/development/ipython_directive.rst +++ b/docs/source/development/ipython_directive.rst @@ -108,7 +108,7 @@ Multi-line input is supported. You can do doctesting on multi-line output as well. Just be careful when using non-deterministic inputs like random numbers in the ipython -directive, because your inputs are ruin through a live interpreter, so +directive, because your inputs are run through a live interpreter, so if you are doctesting random output you will get an error. Here we "seed" the random number generator for deterministic output, and we suppress the seed line so it doesn't show up in the rendered output @@ -172,14 +172,14 @@ the inputs:: .. ipython:: :suppress: - In [144]: from pylab import * + In [144]: from matplotlib.pylab import * In [145]: ion() .. ipython:: :suppress: - In [144]: from pylab import * + In [144]: from matplotlib.pylab import * In [145]: ion() @@ -406,26 +406,26 @@ ipython_savefig_dir ipython_rgxin The compiled regular expression to denote the start of IPython input - lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You + lines. The default is `re.compile('In \[(\d+)\]:\s?(.*)\s*')`. You shouldn't need to change this. ipython_rgxout The compiled regular expression to denote the start of IPython output - lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You + lines. The default is `re.compile('Out\[(\d+)\]:\s?(.*)\s*')`. You shouldn't need to change this. ipython_promptin The string to represent the IPython input prompt in the generated ReST. - The default is 'In [%d]:'. This expects that the line numbers are used + The default is `'In [%d]:'`. This expects that the line numbers are used in the prompt. ipython_promptout The string to represent the IPython prompt in the generated ReST. The - default is 'Out [%d]:'. This expects that the line numbers are used + default is `'Out [%d]:'`. This expects that the line numbers are used in the prompt. From 2e22c3eed952470db51f61e7004b6b2eb2864998 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 17 Oct 2018 17:36:52 -0700 Subject: [PATCH 0391/3726] install matplotlib to build docs --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 56a50838476..1ede58d111f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,3 +4,4 @@ setuptools>=18.5 sphinx sphinx-rtd-theme docrepr +matplotlib From bea9c99bad3e88e9175a0195c2195236832763d4 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 17 Oct 2018 17:47:29 -0700 Subject: [PATCH 0392/3726] try to fix nightly --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index cf184a9d993..2bccb56180a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,11 @@ install: - sudo apt-get install graphviz script: - check-manifest + - | + if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then + # on nightly fake parso known the grammar + cp /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar37.txt /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar38.txt + fi - cd /tmp && iptest --coverage xml && cd - # On the latest Python only, make sure that the docs build. - | From 5dd44c947c6fe89d2f3f902d4ae3375ef1fa3b33 Mon Sep 17 00:00:00 2001 From: Nguyen Duy Hai Date: Sat, 20 Oct 2018 22:53:25 +0700 Subject: [PATCH 0393/3726] Fix indentation for nested block --- IPython/core/inputtransformer2.py | 10 +++++++++- IPython/core/tests/test_inputtransformer2.py | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index e2dd2d08773..b6bbcb1e1fc 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -655,7 +655,15 @@ def check_complete(self, cell: str): if len(tokens_by_line) == 1 and not tokens_by_line[-1]: return 'incomplete', 0 - if tokens_by_line[-1][-1].string == ':': + new_block = False + for token in reversed(tokens_by_line[-1]): + if token.type == tokenize.DEDENT: + continue + elif token.string == ':': + new_block = True + break + + if new_block: # The last line starts a block (e.g. 'if foo:') ix = 0 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}: diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index d6c2fa3bd6b..ea0645238ef 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -207,6 +207,7 @@ def test_check_complete(): cc = ipt2.TransformerManager().check_complete nt.assert_equal(cc("a = 1"), ('complete', None)) nt.assert_equal(cc("for a in range(5):"), ('incomplete', 4)) + nt.assert_equal(cc("for a in range(5):\n if a > 0:"), ('incomplete', 8)) nt.assert_equal(cc("raise = 2"), ('invalid', None)) nt.assert_equal(cc("a = [1,\n2,"), ('incomplete', 0)) nt.assert_equal(cc(")"), ('incomplete', 0)) From dd15f28f49472efd20a10805cf04d17f2d6154f9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 20 Oct 2018 10:45:27 -0700 Subject: [PATCH 0394/3726] Fix miss-capturing of assign statement after a dedent. closes #11415 This fixes a bug where assign statement were miscaptured when occuring after a dedent. This was due to the fact that : >>> '' in '({[' True That is to say the empty string is in any strings. Add a couple of integration tests and unit tests as well, and also add a warning to public function when not used properly, in particular, check that lines passed to make_tokens_by_line do end with an endline marker (at least for the first line), otherwise the function does not behave properly. --- IPython/core/inputtransformer2.py | 19 ++++++----- IPython/core/tests/test_inputtransformer2.py | 33 +++++++++++++++++++- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index e2dd2d08773..b73d701215b 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -13,7 +13,7 @@ from codeop import compile_command import re import tokenize -from typing import List, Tuple +from typing import List, Tuple, Union import warnings _indent_re = re.compile(r'^[ \t]+') @@ -87,7 +87,7 @@ def cell_magic(lines): % (magic_name, first_line, body)] -def _find_assign_op(token_line): +def _find_assign_op(token_line) -> Union[int, None]: """Get the index of the first assignment in the line ('=' not inside brackets) Note: We don't try to support multiple special assignment (a = b = %foo) @@ -97,9 +97,9 @@ def _find_assign_op(token_line): s = ti.string if s == '=' and paren_level == 0: return i - if s in '([{': + if s in {'(','[','{'}: paren_level += 1 - elif s in ')]}': + elif s in {')', ']', '}'}: if paren_level > 0: paren_level -= 1 @@ -449,11 +449,14 @@ def transform(self, lines): return lines_before + [new_line] + lines_after -def make_tokens_by_line(lines): +def make_tokens_by_line(lines:List[str]): """Tokenize a series of lines and group tokens by line. - The tokens for a multiline Python string or expression are - grouped as one line. + The tokens for a multiline Python string or expression are grouped as one + line. All lines except the last lines should keep their line ending ('\\n', + '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)` + for example when passing block of text to this function. + """ # NL tokens are used inside multiline expressions, but also after blank # lines or comments. This is intentional - see https://bugs.python.org/issue17061 @@ -461,6 +464,8 @@ def make_tokens_by_line(lines): # track parentheses level, similar to the internals of tokenize. NEWLINE, NL = tokenize.NEWLINE, tokenize.NL tokens_by_line = [[]] + if len(lines) > 1 and not lines[0].endswith(('\n', '\r', '\r\n', '\x0b', '\x0c')): + warnings.warn("`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified") parenlev = 0 try: for token in tokenize.generate_tokens(iter(lines).__next__): diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index d6c2fa3bd6b..9c92c394e50 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -8,7 +8,7 @@ import string from IPython.core import inputtransformer2 as ipt2 -from IPython.core.inputtransformer2 import make_tokens_by_line +from IPython.core.inputtransformer2 import make_tokens_by_line, _find_assign_op from textwrap import dedent @@ -53,6 +53,22 @@ g() """.splitlines(keepends=True)) +##### + +MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\ +def test(): + for i in range(1): + print(i) + res =! ls +""".splitlines(keepends=True), (4, 7), '''\ +def test(): + for i in range(1): + print(i) + res =get_ipython().getoutput(\' ls\') +'''.splitlines(keepends=True)) + +###### + AUTOCALL_QUOTE = ( [",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'] @@ -103,6 +119,7 @@ [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"] ) + def null_cleanup_transformer(lines): """ A cleanup transform that returns an empty list. @@ -144,18 +161,21 @@ def test_continued_line(): def test_find_assign_magic(): check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False) + check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False) def test_transform_assign_magic(): check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) def test_find_assign_system(): check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None)) check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None)) check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False) def test_transform_assign_system(): check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) def test_find_magic_escape(): check_find(ipt2.EscapedCommand, MULTILINE_MAGIC) @@ -203,6 +223,17 @@ def test_transform_help(): tf = ipt2.HelpEnd((1, 0), (2, 8)) nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2]) +def test_find_assign_op_dedent(): + """ + be carefull that empty token like dedent are not counted as parens + """ + class Tk: + def __init__(self, s): + self.string = s + + nt.assert_equal(_find_assign_op([Tk(s) for s in ('','a','=','b')]), 2) + nt.assert_equal(_find_assign_op([Tk(s) for s in ('','(', 'a','=','b', ')', '=' ,'5')]), 6) + def test_check_complete(): cc = ipt2.TransformerManager().check_complete nt.assert_equal(cc("a = 1"), ('complete', None)) From e3c0214ffa95e3d493fc3dc3ca3078101393c6c9 Mon Sep 17 00:00:00 2001 From: Elyashiv <> Date: Sat, 20 Oct 2018 21:44:23 +0300 Subject: [PATCH 0395/3726] removed expansion of function name when copleting kw-args --- IPython/core/completer.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 9858236905d..c8393f72267 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1538,24 +1538,20 @@ def python_func_kw_matches(self,text): usedNamedArgs.add(token) - # lookup the candidate callable matches either using global_matches - # or attr_matches for dotted names - if len(ids) == 1: - callableMatches = self.global_matches(ids[0]) - else: - callableMatches = self.attr_matches('.'.join(ids[::-1])) argMatches = [] - for callableMatch in callableMatches: - try: - namedArgs = self._default_arguments(eval(callableMatch, - self.namespace)) - except: - continue + try: + callableObj = '.'.join(ids[::-1]) + print(callableObj) + namedArgs = self._default_arguments(eval(callableObj, + self.namespace)) # Remove used named arguments from the list, no need to show twice for namedArg in set(namedArgs) - usedNamedArgs: if namedArg.startswith(text): argMatches.append(u"%s=" %namedArg) + except: + pass + return argMatches def dict_key_matches(self, text): From da83b1b4c7fd423ab7bfca711dd7778bd69b6d7c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 20 Oct 2018 11:54:59 -0700 Subject: [PATCH 0396/3726] Update what's new for 7.1.0 --- .../source/whatsnew/pr/video-width-height.rst | 1 - docs/source/whatsnew/version7.rst | 73 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) delete mode 100644 docs/source/whatsnew/pr/video-width-height.rst diff --git a/docs/source/whatsnew/pr/video-width-height.rst b/docs/source/whatsnew/pr/video-width-height.rst deleted file mode 100644 index 84757f1b430..00000000000 --- a/docs/source/whatsnew/pr/video-width-height.rst +++ /dev/null @@ -1 +0,0 @@ -``IPython.display.Video`` now supports ``width`` and ``height`` arguments, allowing a custom width and height to be set instead of using the video's width and height \ No newline at end of file diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 19352cc0d95..17b3275145d 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,79 @@ 7.x Series ============ +.. _whatsnew710: + +IPython 7.1.0 +============= + + +IPython 7.1.0 is the first minor release after 7.0.0 and mostly bring fixes to +new feature, internal refactor and regressions that happen during the 6.x->7.x +transition. It also bring **Compatibility with Python 3.7.1**, as were +unwillingly relying on a bug in CPython. + +New Core Dev: + + - We welcome Jonathan Slenders to the commiters. Jonathan has done a fantastic + work on Prompt toolkit, and we'd like to recognise his impact by giving him + commit rights. :ghissue:`11397` + +Notable New Features: + + - Restore functionality and documentation of the **sphinx directive**, which is + now stricter (fail on error by default), gained configuration options, have a + brand new documentation page :ref:`ipython_directive`, which need some cleanup. + It is also now *tested* so we hope to have less regressions. + :ghpull:`11402` + + - ``IPython.display.Video`` now supports ``width`` and ``height`` arguments, + allowing a custom width and height to be set instead of using the video's + width and height. :ghpull:`11353` + + - Warn when using ``HTML(' """ - def __init__(self, src, width, height, **kwargs): + def __init__(self, src, width, height, extra="", **kwargs): self.src = src self.width = width self.height = height + self.extra = extra self.params = kwargs def _repr_html_(self): @@ -282,7 +284,9 @@ def _repr_html_(self): return self.iframe.format(src=self.src, width=self.width, height=self.height, - params=params) + params=params, + extra=self.extra) + class YouTubeVideo(IFrame): """Class for embedding a YouTube Video in an IPython session, based on its video id. @@ -310,11 +314,13 @@ class YouTubeVideo(IFrame): will be inserted in the document. """ - def __init__(self, id, width=400, height=300, **kwargs): + def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): self.id=id src = "https://www.youtube.com/embed/{0}".format(id) + if allow_autoplay: + kwargs.update(autoplay=1, extra='allow="autoplay"') super(YouTubeVideo, self).__init__(src, width, height, **kwargs) - + def _repr_jpeg_(self): # Deferred import from urllib.request import urlopen From f1229dff77f6d922f8896ac994446009a82fe551 Mon Sep 17 00:00:00 2001 From: yuji96 Date: Fri, 17 Sep 2021 15:19:34 +0900 Subject: [PATCH 1534/3726] fix broken links due to updated ipyparallel's inventory --- docs/source/development/parallel_connections.rst | 2 +- docs/source/development/parallel_messages.rst | 2 +- docs/source/whatsnew/version0.11.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/development/parallel_connections.rst b/docs/source/development/parallel_connections.rst index db1cccfab61..72532735387 100644 --- a/docs/source/development/parallel_connections.rst +++ b/docs/source/development/parallel_connections.rst @@ -5,4 +5,4 @@ Connection Diagrams of The IPython ZMQ Cluster ============================================== IPython parallel has moved to ipyparallel - -see :ref:`ipyparallel:parallel_connections` for the documentation. +see :ref:`ipyparallel:/reference/connections.md` for the documentation. diff --git a/docs/source/development/parallel_messages.rst b/docs/source/development/parallel_messages.rst index f37ea930f75..180c311b720 100644 --- a/docs/source/development/parallel_messages.rst +++ b/docs/source/development/parallel_messages.rst @@ -5,4 +5,4 @@ Messaging for Parallel Computing ================================ IPython parallel has moved to ipyparallel - -see :ref:`ipyparallel:parallel_messages` for the documentation. +see :ref:`ipyparallel:/reference/messages.md` for the documentation. diff --git a/docs/source/whatsnew/version0.11.rst b/docs/source/whatsnew/version0.11.rst index 05d64478b0b..fc35ae57a49 100644 --- a/docs/source/whatsnew/version0.11.rst +++ b/docs/source/whatsnew/version0.11.rst @@ -309,7 +309,7 @@ be started by calling ``ipython qtconsole``. The protocol is :ref:`documented `. The parallel computing framework has also been rewritten using ZMQ. The -protocol is described :ref:`here `, and the code is in the +protocol is described :ref:`here `, and the code is in the new :mod:`IPython.parallel` module. .. _python3_011: From 941da40ab29389d70f6efc5556d440a7e8716038 Mon Sep 17 00:00:00 2001 From: Mithil Poojary Date: Tue, 14 Sep 2021 08:57:29 +0530 Subject: [PATCH 1535/3726] Use pathlib parent relationships to compare virtualenv directories Fix #13124 --- IPython/core/interactiveshell.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1e11ca6b998..05bd8eea071 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -922,18 +922,7 @@ def init_virtualenv(self): p = Path(os.readlink(p)) paths.append(p.resolve()) - # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible - if str(p_venv).startswith("\\cygdrive"): - p_venv = Path(str(p_venv)[11:]) - elif len(str(p_venv)) >= 2 and str(p_venv)[1] == ":": - p_venv = Path(str(p_venv)[2:]) - - if sys.platform == "win32": - # On Windows there might be a mixture of lower-case and mixed-case paths - p_venv = Path(str(p_venv).lower()) - paths = [Path(str(p).lower()) for p in paths] - - if any(os.fspath(p_venv) in os.fspath(p) for p in paths): + if any(p_venv == p.parents[1] for p in paths): # Our exe is inside or has access to the virtualenv, don't need to do anything. return From 6f4501ca21368992cb3bc1d1027d78b123d7129e Mon Sep 17 00:00:00 2001 From: Mithil Poojary Date: Mon, 20 Sep 2021 08:30:40 +0530 Subject: [PATCH 1536/3726] Restore cygwin path check --- IPython/core/interactiveshell.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 05bd8eea071..d3389a8be0e 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -921,7 +921,11 @@ def init_virtualenv(self): while p.is_symlink(): p = Path(os.readlink(p)) paths.append(p.resolve()) - + + # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible + if str(p_venv).startswith("\\cygdrive"): + p_venv = "C:" / Path(str(p_venv)[11:]) + if any(p_venv == p.parents[1] for p in paths): # Our exe is inside or has access to the virtualenv, don't need to do anything. return From 94240ce5086d648abbdf889e94de537a567817b8 Mon Sep 17 00:00:00 2001 From: Blazej Michalik Date: Tue, 21 Sep 2021 00:17:55 +0200 Subject: [PATCH 1537/3726] Darker --- IPython/core/interactiveshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index d3389a8be0e..ef83ce8621c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -925,7 +925,7 @@ def init_virtualenv(self): # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible if str(p_venv).startswith("\\cygdrive"): p_venv = "C:" / Path(str(p_venv)[11:]) - + if any(p_venv == p.parents[1] for p in paths): # Our exe is inside or has access to the virtualenv, don't need to do anything. return From 49d02f78711c4733d4047ab6c6757f08e1497a90 Mon Sep 17 00:00:00 2001 From: yuji96 Date: Fri, 17 Sep 2021 15:22:31 +0900 Subject: [PATCH 1538/3726] fix coding style with darker --- IPython/lib/display.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index b9e08de9af0..e309398f1ea 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -281,11 +281,13 @@ def _repr_html_(self): params = "?" + urlencode(self.params) else: params = "" - return self.iframe.format(src=self.src, - width=self.width, - height=self.height, - params=params, - extra=self.extra) + return self.iframe.format( + src=self.src, + width=self.width, + height=self.height, + params=params, + extra=self.extra, + ) class YouTubeVideo(IFrame): From b709866b0a3a1108d98b20dd286ab8cd8c9d387f Mon Sep 17 00:00:00 2001 From: yuji96 Date: Fri, 17 Sep 2021 20:37:05 +0900 Subject: [PATCH 1539/3726] replace extra (str) -> extras (iterable) --- IPython/lib/display.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index e309398f1ea..68f9f3ea666 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -8,7 +8,7 @@ from IPython.core.display import DisplayObject, TextDisplayObject -from typing import Tuple +from typing import Tuple, Iterable __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', 'FileLink', 'FileLinks', 'Code'] @@ -263,15 +263,18 @@ class IFrame(object): src="{src}{params}" frameborder="0" allowfullscreen - {extra} + {extras} > """ - def __init__(self, src, width, height, extra="", **kwargs): + def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs): + if extras is None: + extras = [] + self.src = src self.width = width self.height = height - self.extra = extra + self.extras = extras self.params = kwargs def _repr_html_(self): @@ -286,7 +289,7 @@ def _repr_html_(self): width=self.width, height=self.height, params=params, - extra=self.extra, + extras=" ".join(self.extras), ) @@ -320,7 +323,7 @@ def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): self.id=id src = "https://www.youtube.com/embed/{0}".format(id) if allow_autoplay: - kwargs.update(autoplay=1, extra='allow="autoplay"') + kwargs.update(autoplay=1, extras=['allow="autoplay"']) super(YouTubeVideo, self).__init__(src, width, height, **kwargs) def _repr_jpeg_(self): From 1041f44680d312adbec9eb24406a7368c9ed9506 Mon Sep 17 00:00:00 2001 From: yuji96 Date: Fri, 17 Sep 2021 21:19:52 +0900 Subject: [PATCH 1540/3726] don't overwrite extras entered by user --- IPython/lib/display.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 68f9f3ea666..19d4c35383b 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -323,7 +323,8 @@ def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): self.id=id src = "https://www.youtube.com/embed/{0}".format(id) if allow_autoplay: - kwargs.update(autoplay=1, extras=['allow="autoplay"']) + extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] + kwargs.update(autoplay=1, extras=extras) super(YouTubeVideo, self).__init__(src, width, height, **kwargs) def _repr_jpeg_(self): From b7e2c5a885f7f58a865888353caa3efb4c36b116 Mon Sep 17 00:00:00 2001 From: yuji96 Date: Tue, 21 Sep 2021 09:37:14 +0900 Subject: [PATCH 1541/3726] add docs: whats new entry --- .../enable-to-add-extra-attrs-to-iframe.rst | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst diff --git a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst new file mode 100644 index 00000000000..f4f3ad55e02 --- /dev/null +++ b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst @@ -0,0 +1,37 @@ +Enable to add extra attributes to iframe +======================================== + +You can add any extra attributes to the `` + +Using it, you can autoplay ``YouTubeVideo`` by adding ``'allow="autoplay"'``, +even in browsers that disable it by default, such as Google Chrome. +And, you can write it more briefly by using the argument ``allow_autoplay``. +:: + + In [1]: from IPython.display import YouTubeVideo + + In [2]: print(YouTubeVideo("video-id", allow_autoplay=True)._repr_html_()) + + From 66774096f7653c18ba1e0a07142433f17aa9761b Mon Sep 17 00:00:00 2001 From: Blazej Michalik Date: Wed, 22 Sep 2021 00:44:15 +0200 Subject: [PATCH 1542/3726] Reword the YouTubeVideo autoplay WN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should make it easier for the reader to understand the feature and know what to expect. Also, I couldn't help myself. I Never Gonna miss such opportunity. 🎶 Badum-ba-dubaba 🎶 --- .../enable-to-add-extra-attrs-to-iframe.rst | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst index f4f3ad55e02..efb23001a80 100644 --- a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst +++ b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst @@ -1,37 +1,39 @@ -Enable to add extra attributes to iframe -======================================== +``YouTubeVideo`` autoplay and the ability to add extra attributes to ``IFrame`` +=============================================================================== -You can add any extra attributes to the `` +The above cells will result in the following HTML code being displayed in a +notebook:: -Using it, you can autoplay ``YouTubeVideo`` by adding ``'allow="autoplay"'``, -even in browsers that disable it by default, such as Google Chrome. -And, you can write it more briefly by using the argument ``allow_autoplay``. + + +Related to the above, the ``YouTubeVideo`` class now takes an +``allow_autoplay`` flag, which sets up the iframe of the embedded YouTube video +such that it allows autoplay. + +.. note:: + Whether this works depends on whether the autoplay policy of the browser + rendering the HTML allows it. It might not work in every circumstance, and + could get blocked by browser extensions. + +Try it out! :: In [1]: from IPython.display import YouTubeVideo - In [2]: print(YouTubeVideo("video-id", allow_autoplay=True)._repr_html_()) + In [2]: YouTubeVideo("dQw4w9WgXcQ", allow_autoplay=True) - +🙃 From 455e4afda37bfc35312e4d3d14f8d1afbfcfcacc Mon Sep 17 00:00:00 2001 From: Blazej Michalik Date: Wed, 22 Sep 2021 01:03:22 +0200 Subject: [PATCH 1543/3726] Reword a WN entry --- .../whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst index efb23001a80..1954bc439b8 100644 --- a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst +++ b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst @@ -25,9 +25,8 @@ Related to the above, the ``YouTubeVideo`` class now takes an such that it allows autoplay. .. note:: - Whether this works depends on whether the autoplay policy of the browser - rendering the HTML allows it. It might not work in every circumstance, and - could get blocked by browser extensions. + Whether this works depends on the autoplay policy of the browser rendering + the HTML allowing it. It also could get blocked by some browser extensions. Try it out! :: From fe042603646f800f3a2f3516266219448260d5e6 Mon Sep 17 00:00:00 2001 From: Mithil Poojary Date: Wed, 22 Sep 2021 16:33:03 +0530 Subject: [PATCH 1544/3726] Adapt to all sorts of drive names Earlier implementation assumed the drive name to be C and ignored the possibility of it being different. Drive names could also be greater than 1 in length. This possibility is also considered now. --- IPython/core/interactiveshell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ef83ce8621c..7d5b8974380 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -924,7 +924,9 @@ def init_virtualenv(self): # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible if str(p_venv).startswith("\\cygdrive"): - p_venv = "C:" / Path(str(p_venv)[11:]) + p_venv = Path(str(p_venv)) + drive_name = p_venv.parts[2] + p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:]) if any(p_venv == p.parents[1] for p in paths): # Our exe is inside or has access to the virtualenv, don't need to do anything. From 338ffdcaa038358762bef7f10ef91b2ea6fe680c Mon Sep 17 00:00:00 2001 From: Mithil Poojary Date: Wed, 22 Sep 2021 18:17:10 +0530 Subject: [PATCH 1545/3726] Use pathlib comparison over string comparison --- IPython/core/interactiveshell.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 7d5b8974380..37b31580d9c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -923,8 +923,7 @@ def init_virtualenv(self): paths.append(p.resolve()) # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible - if str(p_venv).startswith("\\cygdrive"): - p_venv = Path(str(p_venv)) + if p_venv.parts[1] == "cygdrive": drive_name = p_venv.parts[2] p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:]) From 24a283a9f5a5003c0a6e4f8ccf7ca2268c8eae2c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 24 Sep 2021 15:08:49 -0700 Subject: [PATCH 1546/3726] What's new 7.28 --- docs/source/whatsnew/version7.rst | 49 ++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 7dd68592232..84fccd20c53 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,53 @@ 7.x Series ============ +.. _version 7.28: + +IPython 7.28 +============ + + +IPython 7.28 is again a minor release that mostly bring bugfixes, and couple of +improvement. Many thanks to MrMino, who again did all the work this month, and +made a number of documentation improvements. + +Here is a non-exhaustive list of changes, + +Fixes: + + - async with doesn't allow newlines :ghpull:`13090` + - Dynamically changing to vi mode via %config magic) :ghpull:`13091` + +Virtualenv handling fixes: + + - init_virtualenv now uses Pathlib :ghpull:`12548` + - Fix Improper path comparison of virtualenv directories :ghpull:`13140` + - Fix virtual environment user warning for lower case pathes :ghpull:`13094` + - Adapt to all sorts of drive names for cygwin :ghpull:`13153` + +New Features: + + - enable autoplay in embed YouTube player :ghpull:`13133` + + Documentation: + + - Fix formatting for the core.interactiveshell documentation :ghpull:`13118` + - Fix broken ipyparallel's refs :ghpull:`13138` + - Improve formatting of %time documentation :ghpull:`13125` + - Reword the YouTubeVideo autoplay WN :ghpull:`13147` + + +Thanks +------ + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + .. _version 7.27: IPython 7.27 @@ -18,7 +65,7 @@ Thanks Many thanks to all the contributors to this release. You can find all individual contributions to this milestone `on github -`__. +`__. Thanks as well to the `D. E. Shaw group `__ for sponsoring work on IPython and related libraries. From 20281a0140e3cf3c73180cae90bb50a7b4879324 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 24 Sep 2021 14:55:30 -0700 Subject: [PATCH 1547/3726] Add history file to debugger. This adds a configurable `InteractiveShell.debugger_history_file=...` which default to `~/.pdbhistory`, that store what is typed in ipdb; this make it easy to persist across sessions. Some of the logic is moved into the debugger itself so that existance and creation of file is used only once Pdb is started. --- IPython/terminal/debugger.py | 15 +++++++++++++++ IPython/terminal/interactiveshell.py | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 57aa5dd90d1..5c9c3612bb6 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -1,18 +1,22 @@ import asyncio +import os import sys import threading from IPython.core.debugger import Pdb + from IPython.core.completer import IPCompleter from .ptutils import IPythonPTCompleter from .shortcuts import create_ipython_shortcuts from . import embed +from pathlib import Path from pygments.token import Token from prompt_toolkit.shortcuts.prompt import PromptSession from prompt_toolkit.enums import EditingMode from prompt_toolkit.formatted_text import PygmentsTokens +from prompt_toolkit.history import InMemoryHistory, FileHistory from prompt_toolkit import __version__ as ptk_version PTK3 = ptk_version.startswith('3.') @@ -55,6 +59,17 @@ def gen_comp(self, text): self._ptcomp = IPythonPTCompleter(compl) + # setup history only when we start pdb + if self.shell.debugger_history is None: + if self.shell.debugger_history_file is not None: + + p = Path(self.shell.debugger_history_file).expanduser() + if not p.exists(): + p.touch() + self.debugger_history = FileHistory(os.path.expanduser(str(p))) + else: + self.debugger_history = InMemoryHistory() + options = dict( message=(lambda: PygmentsTokens(get_prompt_tokens())), editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 27144124074..b73a140f751 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -124,6 +124,10 @@ class TerminalInteractiveShell(InteractiveShell): pt_app = None debugger_history = None + debugger_history_file = Unicode( + "~/.pdbhistory", help="File in which to store and read history" + ).tag(config=True) + simple_prompt = Bool(_use_simple_prompt, help="""Use `raw_input` for the REPL, without completion and prompt colors. @@ -566,7 +570,6 @@ def __init__(self, *args, **kwargs): self.init_term_title() self.keep_running = True - self.debugger_history = InMemoryHistory() def ask_exit(self): self.keep_running = False From b27ed6b5e5aa8ab176e8025e726c72e60c5e0c9e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 20 Sep 2021 15:33:08 -0700 Subject: [PATCH 1548/3726] Allow decorator frames to be marked as skippable. When done so, by default pdb will step over those frames and directly into the decorated functions. >>> def helper_1(): ... print("don't step in me") ... ... ... def helper_2(): ... print("in me neither") ... One can define a decorator that wrap a function between the two helpers: >>> def pdb_skipped_decorator(function): ... ... ... def wrapped_fn(*args, **kwargs): ... __debuggerskip__ = True ... helper_1() ... __debuggerskip__ = False ... result = function(*args, **kwargs) ... __debuggerskip__ = True ... helper_2() ... return result ... ... return wrapped_fn When decorating a function, ipdb will directly step into ``bar()`` by default: >>> @foo_decorator ... def bar(x, y): ... return x * y You can toggle the behavior with ipdb> skip_predicates debuggerskip False or configure it in your ``.pdbrc`` --- IPython/core/debugger.py | 134 ++++++++++++++++++++++++++-- IPython/core/tests/test_debugger.py | 112 +++++++++++++++++++++++ IPython/terminal/debugger.py | 4 +- docs/source/whatsnew/version7.rst | 3 +- 4 files changed, 243 insertions(+), 10 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index e2676a61de7..cbad8ff353b 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -2,6 +2,71 @@ """ Pdb debugger class. + +This is an extension to PDB which adds a number of new features. +Note that there is also the `IPython.terminal.debugger` class which provides UI +improvements. + +We also strongly recommend to use this via the `ipdb` package, which provides +extra configuration options. + +Amoung other this subclass of PDB expose here: + - support many IPython magics like pdef/psource. + - allow to hide some frames in tracebacks + - allow to skip some frames. + +The skipping and hiding frames are configurable via the `skip_predicates` +command. + +By default, frames from readonly files will be hidden, frames containing +``__tracebackhide__=True`` will be hidden. + +Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent +frames value of ``__debuggerskip__`` is ``True`` will be skipped. + + >>> def helper_1(): + ... print("don't step in me") + ... + ... + ... def helper_2(): + ... print("in me neither") + ... + +One can define a decorator that wrap a function between the two helpers: + + >>> def pdb_skipped_decorator(function): + ... + ... + ... def wrapped_fn(*args, **kwargs): + ... __debuggerskip__ = True + ... helper_1() + ... __debuggerskip__ = False + ... result = function(*args, **kwargs) + ... __debuggerskip__ = True + ... helper_2() + ... return result + ... + ... return wrapped_fn + +When decorating a function, ipdb will directly step into ``bar()`` by +default: + + >>> @foo_decorator + ... def bar(x, y): + ... return x * y + + +You can toggle the behavior with + + ipdb> skip_predicates debuggerskip false + +or configure it in your ``.pdbrc`` + + + +Licencse +-------- + Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't damaged. @@ -9,11 +74,16 @@ In the future, this class will be expanded with improvements over the standard pdb. -The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor -changes. Licensing should therefore be under the standard Python terms. For -details on the PSF (Python Software Foundation) standard license, see: +The original code in this file is mainly lifted out of cmd.py in Python 2.2, +with minor changes. Licensing should therefore be under the standard Python +terms. For details on the PSF (Python Software Foundation) standard license, +see: https://docs.python.org/2/license.html + + +All the changes since then are under the same license as IPython. + """ #***************************************************************************** @@ -51,6 +121,8 @@ # it does so with some limitations. The rest of this support is implemented in # the Tracer constructor. +DEBUGGERSKIP = "__debuggerskip__" + def make_arrow(pad): """generate the leading arrow in front of traceback or debugger""" @@ -206,7 +278,12 @@ class Pdb(OldPdb): """ - default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True} + default_predicates = { + "tbhide": True, + "readonly": False, + "ipython_internal": True, + "debuggerskip": True, + } def __init__(self, color_scheme=None, completekey=None, stdin=None, stdout=None, context=5, **kwargs): @@ -303,7 +380,9 @@ def __init__(self, color_scheme=None, completekey=None, # list of predicates we use to skip frames self._predicates = self.default_predicates + self._skipping = False + # def set_colors(self, scheme): """Shorthand access to the color table scheme selector method.""" self.color_scheme_table.set_active_scheme(scheme) @@ -815,7 +894,50 @@ def do_where(self, arg): do_w = do_where + def break_anywhere(self, frame): + """ + + _stop_in_decorator_internals is overly restrictive, as we may still want + to trace function calls, so we need to also update break_anywhere so + that is we don't `stop_here`, because of debugger skip, we may still + stop at any point inside the function + + """ + if self._predicates["debuggerskip"]: + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): + return True + return super().break_anywhere(frame) + + @skip_doctest + def _is_in_decorator_internal_and_should_skip(self, frame): + """ + Utility to tell us whether we are in a decorator internal and should stop. + + + + """ + + # if we are disable don't skip + if not self._predicates["debuggerskip"]: + return False + + # if frame is tagged, skip by default. + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + + # if parent frame value set to True skip as well. + if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): + return True + + return False + def stop_here(self, frame): + + if self._is_in_decorator_internal_and_should_skip(frame) is True: + return False + hidden = False if self.skip_hidden: hidden = self._hidden_predicate(frame) @@ -938,10 +1060,10 @@ def do_context(self, context): class InterruptiblePdb(Pdb): """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" - def cmdloop(self): + def cmdloop(self, intro=None): """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" try: - return OldPdb.cmdloop(self) + return OldPdb.cmdloop(self, intro=intro) except KeyboardInterrupt: self.stop_here = lambda frame: False self.do_quit("") diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 35e77e4acd5..217eb6bd3fa 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -323,6 +323,118 @@ def g(): child.close() +skip_decorators_blocks = ( + """ + def helper_1(): + pass # should not stop here + """, + """ + def helper_2(): + pass # should not stop here + """, + """ + def pdb_skipped_decorator(function): + def wrapped_fn(*args, **kwargs): + __debuggerskip__ = True + helper_1() + __debuggerskip__ = False + result = function(*args, **kwargs) + __debuggerskip__ = True + helper_2() + return result + return wrapped_fn + """, + """ + @pdb_skipped_decorator + def bar(x, y): + return x * y + """, + """import IPython.terminal.debugger as ipdb""", + """ + def f(): + ipdb.set_trace() + bar(3, 4) + """, + """ + f() + """, +) + + +def _decorator_skip_setup(): + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + + dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks] + in_prompt_number = 1 + for cblock in dedented_blocks: + child.expect_exact(f"In [{in_prompt_number}]:") + in_prompt_number += 1 + for line in cblock.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.sendline("") + return child + + +@skip_win32 +def test_decorator_skip(): + """test that decorator frames can be skipped.""" + + child = _decorator_skip_setup() + + child.expect_exact("3 bar(3, 4)") + child.expect("ipdb>") + + child.expect("ipdb>") + child.sendline("step") + child.expect_exact("step") + + child.expect_exact("1 @pdb_skipped_decorator") + + child.sendline("s") + child.expect_exact("return x * y") + + child.close() + + +@skip_win32 +def test_decorator_skip_disabled(): + """test that decorator frame skipping can be disabled""" + + child = _decorator_skip_setup() + + child.expect_exact("3 bar(3, 4)") + + for input_, expected in [ + ("skip_predicates debuggerskip False", ""), + ("skip_predicates", "debuggerskip : False"), + ("step", "---> 2 def wrapped_fn"), + ("step", "----> 3 __debuggerskip__"), + ("step", "----> 4 helper_1()"), + ("step", "---> 1 def helper_1():"), + ("next", "----> 2 pass"), + ("next", "--Return--"), + ("next", "----> 5 __debuggerskip__ = False"), + ]: + child.expect("ipdb>") + child.sendline(input_) + child.expect_exact(input_) + child.expect_exact(expected) + + child.close() + + @skip_win32 def test_where_erase_value(): """Test that `where` does not access f_locals and erase values.""" diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 57aa5dd90d1..aec1a7fbb03 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -64,7 +64,7 @@ def gen_comp(self, text): enable_history_search=True, mouse_support=self.shell.mouse_support, complete_style=self.shell.pt_complete_style, - style=self.shell.style, + style=getattr(self.shell, "style", None), color_depth=self.shell.color_depth, ) @@ -89,7 +89,6 @@ def cmdloop(self, intro=None): # prompt itself in a different thread (we can't start an event loop # within an event loop). This new thread won't have any event loop # running, and here we run our prompt-loop. - self.preloop() try: @@ -124,7 +123,6 @@ def in_thread(): if keyboard_interrupt: raise KeyboardInterrupt - line = self.precmd(line) stop = self.onecmd(line) stop = self.postcmd(stop, line) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 84fccd20c53..990e0eb7021 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,7 @@ 7.x Series ============ + .. _version 7.28: IPython 7.28 @@ -543,7 +544,7 @@ Change of API and exposed objects automatically detected using `frappuccino `_ (still in beta): -The following items are new and mostly related to understanding ``__tracebackbide__``:: +The following items are new and mostly related to understanding ``__tracebackhide__``:: + IPython.core.debugger.Pdb.do_down(self, arg) + IPython.core.debugger.Pdb.do_skip_hidden(self, arg) From d03a6c072aaca409219b53ba04f8f4262a41d5ac Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Sep 2021 09:07:29 -0700 Subject: [PATCH 1549/3726] Apply suggestions from code review Co-authored-by: Blazej Michalik <6691643+MrMino@users.noreply.github.com> --- IPython/core/debugger.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index cbad8ff353b..11f1bec534e 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -10,10 +10,10 @@ We also strongly recommend to use this via the `ipdb` package, which provides extra configuration options. -Amoung other this subclass of PDB expose here: - - support many IPython magics like pdef/psource. - - allow to hide some frames in tracebacks - - allow to skip some frames. +Among other things, this subclass of PDB: + - supports many IPython magics like pdef/psource + - hide frames in tracebacks based on `__tracebackhide__` + - allows to skip frames based on `__debuggerskip__` The skipping and hiding frames are configurable via the `skip_predicates` command. @@ -32,7 +32,7 @@ ... print("in me neither") ... -One can define a decorator that wrap a function between the two helpers: +One can define a decorator that wraps a function between the two helpers: >>> def pdb_skipped_decorator(function): ... @@ -380,7 +380,6 @@ def __init__(self, color_scheme=None, completekey=None, # list of predicates we use to skip frames self._predicates = self.default_predicates - self._skipping = False # def set_colors(self, scheme): @@ -919,7 +918,7 @@ def _is_in_decorator_internal_and_should_skip(self, frame): """ - # if we are disable don't skip + # if we are disabled don't skip if not self._predicates["debuggerskip"]: return False From 4a2e85faf8e30c70a5d2c1af6bb45e2cdee5a067 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 29 Sep 2021 09:37:13 +0200 Subject: [PATCH 1550/3726] print_figure return base64 str instead of bytes sending bytes relies on serializer's `default` functionality, where we can just send the right thing here this used to be ambiguous on Python 2, but not anymore --- IPython/core/pylabtools.py | 54 ++++++++++++++++++++------- IPython/core/tests/test_display.py | 6 ++- IPython/core/tests/test_pylabtools.py | 14 +++++-- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 35f79249f7d..3ad575ae931 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -5,6 +5,8 @@ # Distributed under the terms of the Modified BSD License. from io import BytesIO +from binascii import b2a_base64 +from functools import partial import warnings from IPython.core.display import _pngxy @@ -99,7 +101,7 @@ def figsize(sizex, sizey): matplotlib.rcParams['figure.figsize'] = [sizex, sizey] -def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): +def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs): """Print a figure to an image, and return the resulting file data Returned data will be bytes unless ``fmt='svg'``, @@ -107,6 +109,12 @@ def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): Any keyword args are passed to fig.canvas.print_figure, such as ``quality`` or ``bbox_inches``. + + If `base64` is True, return base64-encoded str instead of raw bytes + for binary-encoded image formats + + .. versionadded: 7.29 + base64 argument """ # When there's an empty figure, we shouldn't return anything, otherwise we # get big blank areas in the qt console. @@ -138,19 +146,31 @@ def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): data = bytes_io.getvalue() if fmt == 'svg': data = data.decode('utf-8') + elif base64: + data = b2a_base64(data).decode("ascii") return data -def retina_figure(fig, **kwargs): - """format a figure as a pixel-doubled (retina) PNG""" - pngdata = print_figure(fig, fmt='retina', **kwargs) +def retina_figure(fig, base64=False, **kwargs): + """format a figure as a pixel-doubled (retina) PNG + + If `base64` is True, return base64-encoded str instead of raw bytes + for binary-encoded image formats + + .. versionadded: 7.29 + base64 argument + """ + pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs) # Make sure that retina_figure acts just like print_figure and returns # None when the figure is empty. if pngdata is None: return w, h = _pngxy(pngdata) metadata = {"width": w//2, "height":h//2} + if base64: + pngdata = b2a_base64(pngdata).decode("ascii") return pngdata, metadata + # We need a little factory function here to create the closure where # safe_execfile can live. def mpl_runner(safe_execfile): @@ -249,16 +269,22 @@ def select_figure_formats(shell, formats, **kwargs): gs = "%s" % ','.join([repr(f) for f in supported]) raise ValueError("supported formats are: %s not %s" % (gs, bs)) - if 'png' in formats: - png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs)) - if 'retina' in formats or 'png2x' in formats: - png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs)) - if 'jpg' in formats or 'jpeg' in formats: - jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs)) - if 'svg' in formats: - svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs)) - if 'pdf' in formats: - pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs)) + if "png" in formats: + png_formatter.for_type( + Figure, partial(print_figure, fmt="png", base64=True, **kwargs) + ) + if "retina" in formats or "png2x" in formats: + png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs)) + if "jpg" in formats or "jpeg" in formats: + jpg_formatter.for_type( + Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs) + ) + if "svg" in formats: + svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs)) + if "pdf" in formats: + pdf_formatter.for_type( + Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs) + ) #----------------------------------------------------------------------------- # Code for initializing matplotlib and importing pylab diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index 5c9745d9d5d..f7e9a52f081 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -187,10 +187,12 @@ def test_set_matplotlib_formats_kwargs(): display.set_matplotlib_formats('png', **kwargs) formatter = ip.display_formatter.formatters['image/png'] f = formatter.lookup_by_type(Figure) - cell = f.__closure__[0].cell_contents + formatter_kwargs = f.keywords expected = kwargs + expected["base64"] = True + expected["fmt"] = "png" expected.update(cfg.print_figure_kwargs) - nt.assert_equal(cell, expected) + nt.assert_equal(formatter_kwargs, expected) def test_display_available(): """ diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 9a6b2f7920b..426930d54fa 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -5,7 +5,8 @@ # Distributed under the terms of the Modified BSD License. -from io import UnsupportedOperation, BytesIO +from binascii import a2b_base64 +from io import BytesIO import matplotlib matplotlib.use('Agg') @@ -104,8 +105,11 @@ def test_select_figure_formats_kwargs(): pt.select_figure_formats(ip, 'png', **kwargs) formatter = ip.display_formatter.formatters['image/png'] f = formatter.lookup_by_type(Figure) - cell = f.__closure__[0].cell_contents - nt.assert_equal(cell, kwargs) + cell = f.keywords + expected = kwargs + expected["base64"] = True + expected["fmt"] = "png" + assert cell == expected # check that the formatter doesn't raise fig = plt.figure() @@ -114,7 +118,9 @@ def test_select_figure_formats_kwargs(): plt.draw() formatter.enabled = True png = formatter(fig) - assert png.startswith(_PNG) + assert isinstance(png, str) + png_bytes = a2b_base64(png) + assert png_bytes.startswith(_PNG) def test_select_figure_formats_set(): ip = get_ipython() From 913f3d1ca03f6dffe1eee4069976f05f17759cee Mon Sep 17 00:00:00 2001 From: Jakub Date: Sat, 2 Oct 2021 20:36:59 +0200 Subject: [PATCH 1551/3726] Convert WindowsPath to str in init_virtualenv. --- IPython/core/interactiveshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 37b31580d9c..e7d152a0a73 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -932,7 +932,7 @@ def init_virtualenv(self): return if sys.platform == "win32": - virtual_env = Path(os.environ["VIRTUAL_ENV"], "Lib", "site-packages") + virtual_env = str(Path(os.environ["VIRTUAL_ENV"], "Lib", "site-packages")) else: virtual_env_path = Path( os.environ["VIRTUAL_ENV"], "lib", "python{}.{}", "site-packages" From eadea3724bd66adf616bba9a0fefcac20695d583 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sun, 3 Oct 2021 10:44:00 +0800 Subject: [PATCH 1552/3726] use _exec wrapper for qt win32 inputhook --- IPython/terminal/pt_inputhooks/qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/terminal/pt_inputhooks/qt.py b/IPython/terminal/pt_inputhooks/qt.py index bc34f70a43c..f1e710aff54 100644 --- a/IPython/terminal/pt_inputhooks/qt.py +++ b/IPython/terminal/pt_inputhooks/qt.py @@ -64,7 +64,7 @@ def inputhook(context): timer.timeout.connect(event_loop.quit) while not context.input_is_ready(): timer.start(50) # 50 ms - event_loop.exec_() + _exec(event_loop) timer.stop() else: # On POSIX platforms, we can use a file descriptor to quit the event From f6851e4f0732aa5f099428e6c9ec047f59ce1c96 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sun, 3 Oct 2021 22:15:22 +0200 Subject: [PATCH 1553/3726] Fix cpaste documentation - The code was example was invalid Python3 - IPython magics are supported - Added example using a magic Closes #12930 --- IPython/terminal/magics.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/IPython/terminal/magics.py b/IPython/terminal/magics.py index 9e6f6472303..c5d382e82a3 100644 --- a/IPython/terminal/magics.py +++ b/IPython/terminal/magics.py @@ -109,7 +109,7 @@ def cpaste(self, parameter_s=''): Just press enter and type -- (and press enter again) and the block will be what was just pasted. - IPython statements (magics, shell escapes) are not supported (yet). + Shell escapes are not supported (yet). See also -------- @@ -122,9 +122,19 @@ def cpaste(self, parameter_s=''): In [8]: %cpaste Pasting code; enter '--' alone on the line to stop. :>>> a = ["world!", "Hello"] - :>>> print " ".join(sorted(a)) + :>>> print(" ".join(sorted(a))) :-- Hello world! + + :: + In [8]: %cpaste + Pasting code; enter '--' alone on the line to stop. + :>>> %alias_magic t timeit + :>>> %t -n1 pass + :-- + Created `%t` as an alias for `%timeit`. + Created `%%t` as an alias for `%%timeit`. + 354 ns ± 224 ns per loop (mean ± std. dev. of 7 runs, 1 loop each) """ opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') if 'r' in opts: From 65c34c49df75dce643a87f2ecee39c3a5f9aa31a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 4 Oct 2021 09:07:06 -0700 Subject: [PATCH 1554/3726] Expand and Fix PDB skip. This expand and fix the logic arround PBD skip. 1) it should support nested decorators, as long as they are all marked. 2) it will stop into breakpoints if those are set even in debuggerskip. 3) Documentation mentioned those facts. --- IPython/core/debugger.py | 23 +++++++-- IPython/core/tests/test_debugger.py | 78 ++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 11f1bec534e..0083f537227 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -24,8 +24,12 @@ Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent frames value of ``__debuggerskip__`` is ``True`` will be skipped. - >>> def helper_1(): + >>> def helpers_helper(): + ... pass + ... + ... def helper_1(): ... print("don't step in me") + ... helpers_helpers() # will be stepped over unless breakpoint set. ... ... ... def helper_2(): @@ -44,6 +48,7 @@ ... result = function(*args, **kwargs) ... __debuggerskip__ = True ... helper_2() + ... # setting __debuggerskip__ to False again is not necessary ... return result ... ... return wrapped_fn @@ -902,12 +907,16 @@ def break_anywhere(self, frame): stop at any point inside the function """ + + sup = super().break_anywhere(frame) + if sup: + return sup if self._predicates["debuggerskip"]: if DEBUGGERSKIP in frame.f_code.co_varnames: return True if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): return True - return super().break_anywhere(frame) + return False @skip_doctest def _is_in_decorator_internal_and_should_skip(self, frame): @@ -926,9 +935,13 @@ def _is_in_decorator_internal_and_should_skip(self, frame): if DEBUGGERSKIP in frame.f_code.co_varnames: return True - # if parent frame value set to True skip as well. - if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): - return True + # if one of the parent frame value set to True skip as well. + + cframe = frame + while getattr(cframe, "f_back", None): + cframe = cframe.f_back + if self._get_frame_locals(cframe).get(DEBUGGERSKIP): + return True return False diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 217eb6bd3fa..7e1a07d5fb1 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -12,6 +12,7 @@ import sys import time import warnings + from subprocess import PIPE, CalledProcessError, check_output from tempfile import NamedTemporaryFile from textwrap import dedent @@ -324,15 +325,31 @@ def g(): skip_decorators_blocks = ( + """ + def helpers_helper(): + pass # should not stop here except breakpoint + """, """ def helper_1(): - pass # should not stop here + helpers_helper() # should not stop here """, """ def helper_2(): pass # should not stop here """, """ + def pdb_skipped_decorator2(function): + def wrapped_fn(*args, **kwargs): + __debuggerskip__ = True + helper_2() + __debuggerskip__ = False + result = function(*args, **kwargs) + __debuggerskip__ = True + helper_2() + return result + return wrapped_fn + """, + """ def pdb_skipped_decorator(function): def wrapped_fn(*args, **kwargs): __debuggerskip__ = True @@ -346,6 +363,7 @@ def wrapped_fn(*args, **kwargs): """, """ @pdb_skipped_decorator + @pdb_skipped_decorator2 def bar(x, y): return x * y """, @@ -423,7 +441,7 @@ def test_decorator_skip_disabled(): ("step", "----> 3 __debuggerskip__"), ("step", "----> 4 helper_1()"), ("step", "---> 1 def helper_1():"), - ("next", "----> 2 pass"), + ("next", "----> 2 helpers_helper()"), ("next", "--Return--"), ("next", "----> 5 __debuggerskip__ = False"), ]: @@ -435,6 +453,62 @@ def test_decorator_skip_disabled(): child.close() +@skip_win32 +def test_decorator_skip_with_breakpoint(): + """test that decorator frame skipping can be disabled""" + + import pexpect + + env = os.environ.copy() + env["IPY_TEST_SIMPLE_PROMPT"] = "1" + + child = pexpect.spawn( + sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env + ) + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + + child.expect("IPython") + child.expect("\n") + + ### we need a filename, so we need to exec the full block with a filename + with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf: + + name = tf.name[:-3].split("/")[-1] + tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode()) + tf.flush() + codeblock = f"from {name} import f" + + dedented_blocks = [ + codeblock, + "f()", + ] + + in_prompt_number = 1 + for cblock in dedented_blocks: + child.expect_exact(f"In [{in_prompt_number}]:") + in_prompt_number += 1 + for line in cblock.splitlines(): + child.sendline(line) + child.expect_exact(line) + child.sendline("") + + # as the filename does not exists, we'll rely on the filename prompt + child.expect_exact("47 bar(3, 4)") + + for input_, expected in [ + (f"b {name}.py:3", ""), + ("step", "1---> 3 pass # should not stop here except"), + ("step", "---> 38 @pdb_skipped_decorator"), + ("continue", ""), + ]: + child.expect("ipdb>") + child.sendline(input_) + child.expect_exact(input_) + child.expect_exact(expected) + + child.close() + + @skip_win32 def test_where_erase_value(): """Test that `where` does not access f_locals and erase values.""" From 9d184baa2f8d162cba3ea3b363a8f3453b2d4e36 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 6 Oct 2021 20:00:17 -0400 Subject: [PATCH 1555/3726] FIX: make sure all of the Qt backends map to the qt event loop --- IPython/core/pylabtools.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 3ad575ae931..7ebcf1e6df6 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -41,8 +41,6 @@ # most part it's just a reverse of the above dict, but we also need to add a # few others that map to the same GUI manually: backend2gui = dict(zip(backends.values(), backends.keys())) -# Our tests expect backend2gui to just return 'qt' -backend2gui['Qt4Agg'] = 'qt' # In the reverse mapping, there are a few extra valid matplotlib backends that # map to the same GUI support backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk" @@ -50,6 +48,13 @@ backend2gui["GTK4Cairo"] = "gtk4" backend2gui["WX"] = "wx" backend2gui["CocoaAgg"] = "osx" +# There needs to be a hysteresis here as the new QtAgg Matplotlib backend +# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5, +# and Qt6. +backend2gui["QtAgg"] = "qt" +backend2gui["Qt4Agg"] = "qt" +backend2gui["Qt5Agg"] = "qt" + # And some backends that don't need GUI integration del backend2gui["nbAgg"] del backend2gui["agg"] From 5431ad665205af68171ac51e2e364f67bc6d450f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 6 Oct 2021 20:05:35 -0400 Subject: [PATCH 1556/3726] FIX: ipympl does not need event loop support --- IPython/core/pylabtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 7ebcf1e6df6..36b7276b8b2 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -62,6 +62,7 @@ del backend2gui["pdf"] del backend2gui["ps"] del backend2gui["module://matplotlib_inline.backend_inline"] +del backend2gui["module://ipympl.backend_nbagg"] #----------------------------------------------------------------------------- # Matplotlib utilities From 7a20999bb2097a781b81e3098ba3e379cdd6b14e Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Fri, 8 Oct 2021 08:43:44 +0000 Subject: [PATCH 1557/3726] create ipython_dir if not exists --- IPython/paths.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/paths.py b/IPython/paths.py index 43146b0bb75..a2ea5bc1159 100644 --- a/IPython/paths.py +++ b/IPython/paths.py @@ -71,6 +71,8 @@ def get_ipython_dir() -> str: warn("IPython parent '{0}' is not a writable location," " using a temp directory.".format(parent)) ipdir = tempfile.mkdtemp() + else: + os.makedirs(ipdir) assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not." return ipdir From 26b7363f293141541ad7ec003a26a48b82392454 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Fri, 8 Oct 2021 09:48:36 +0000 Subject: [PATCH 1558/3726] update test case for writable folder --- IPython/core/tests/test_paths.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/IPython/core/tests/test_paths.py b/IPython/core/tests/test_paths.py index ab1c4132a8e..524a74d18a0 100644 --- a/IPython/core/tests/test_paths.py +++ b/IPython/core/tests/test_paths.py @@ -160,6 +160,10 @@ def test_get_ipython_dir_7(): @skip_win32 def test_get_ipython_dir_8(): """test_get_ipython_dir_8, test / home directory""" + if not os.path.isdir('/') or not os.access('/', os.W_OK): + # test only when HOME directory actually writable + return + with patch.object(paths, '_writable_dir', lambda path: bool(path)), \ patch.object(paths, 'get_xdg_dir', return_value=None), \ modified_env({ From 7289630f5c158551084dbfe2e001da3eca8db4b9 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Fri, 8 Oct 2021 09:51:45 +0000 Subject: [PATCH 1559/3726] rm redundant if --- IPython/core/tests/test_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_paths.py b/IPython/core/tests/test_paths.py index 524a74d18a0..6b39b9c1791 100644 --- a/IPython/core/tests/test_paths.py +++ b/IPython/core/tests/test_paths.py @@ -160,7 +160,7 @@ def test_get_ipython_dir_7(): @skip_win32 def test_get_ipython_dir_8(): """test_get_ipython_dir_8, test / home directory""" - if not os.path.isdir('/') or not os.access('/', os.W_OK): + if not os.access('/', os.W_OK): # test only when HOME directory actually writable return From 397338f0ee4fcbcab962d3153d791250a1f7de3f Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Fri, 8 Oct 2021 10:00:53 +0000 Subject: [PATCH 1560/3726] lint - use double quote --- IPython/core/tests/test_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/tests/test_paths.py b/IPython/core/tests/test_paths.py index 6b39b9c1791..2182cb7cb0c 100644 --- a/IPython/core/tests/test_paths.py +++ b/IPython/core/tests/test_paths.py @@ -160,7 +160,7 @@ def test_get_ipython_dir_7(): @skip_win32 def test_get_ipython_dir_8(): """test_get_ipython_dir_8, test / home directory""" - if not os.access('/', os.W_OK): + if not os.access("/", os.W_OK): # test only when HOME directory actually writable return From f19f594066bb6821efb698b0c14de3ba8a1e88e6 Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Sat, 9 Oct 2021 14:12:04 -0500 Subject: [PATCH 1561/3726] ci: Add schedule and workflow_dispatch support --- .github/workflows/test.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9bbae48cf7..9f9d938969c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,13 @@ name: Run tests -on: [push, pull_request] +on: + push: + pull_request: + # Run weekly on Monday at 1:23 UTC + schedule: + - cron: '23 1 * * 1' + workflow_dispatch: + jobs: test: From b010c15d1134a90c44ef07c22e821266dcc8bd8e Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Sat, 9 Oct 2021 14:18:31 -0500 Subject: [PATCH 1562/3726] ci: Add testing for macOS on ends --- .github/workflows/test.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f9d938969c..c98d24ef823 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,10 +11,17 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9] + os: [ubuntu-latest] + python-version: ["3.7", "3.8", "3.9"] + # Test all on ubuntu, test ends on macos + include: + - os: macos-latest + python-version: "3.7" + - os: macos-latest + python-version: "3.9" steps: - uses: actions/checkout@v2 @@ -40,4 +47,5 @@ jobs: run: | pytest - name: Upload coverage to Codecov + if: matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v1 From 3158a1434dbf9c460431b4b35ab09e5caf754089 Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Sat, 9 Oct 2021 14:19:35 -0500 Subject: [PATCH 1563/3726] ci: Remove now redundant workflow --- .github/workflows/test-osx.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/test-osx.yml diff --git a/.github/workflows/test-osx.yml b/.github/workflows/test-osx.yml deleted file mode 100644 index c9afbe0a0ea..00000000000 --- a/.github/workflows/test-osx.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Run tests on OSX - -on: [push, pull_request] - -jobs: - test: - runs-on: macos-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.7 - uses: actions/setup-python@v2 - with: - python-version: 3.7 - - name: Install and update Python dependencies - run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade -e file://$PWD#egg=ipython[test] - python -m pip install --upgrade --upgrade-strategy eager trio curio - python -m pip install --upgrade pytest pytest-trio 'matplotlib!=3.2.0' - python -m pip install --upgrade anyio - - name: pytest - run: pytest From 02901f75b7fbf2bed3b2034909d2a8041a99a3b9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 11 Oct 2021 10:01:39 -0700 Subject: [PATCH 1564/3726] Update .github/workflows/test.yml --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c98d24ef823..2788e8640a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,5 +47,4 @@ jobs: run: | pytest - name: Upload coverage to Codecov - if: matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v1 From 27987a540065895922fc53b6b0abf6b497eef757 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 17 Oct 2021 12:42:52 +0100 Subject: [PATCH 1565/3726] Use codecov Github action v2 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2788e8640a8..2ac1b746a84 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,4 +47,4 @@ jobs: run: | pytest - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 From 25f92b9988c81b70b444e88b4afc5aab8a875d8e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 16 Oct 2021 11:34:47 -0700 Subject: [PATCH 1566/3726] Try to fix some of current failures. See error found in CI below. Hopefully this fixes it. There are manycomplexity, we need to make sure the loop has no task after each tests, so we need to close and restore the subprocess watcher. We make it a context manager and provide a decorator. ====================================================================== ERROR: IPython.core.tests.test_magic.test_script_out ---------------------------------------------------------------------- Traceback (most recent call last): File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/nose/case.py", line 198, in runTest self.test(*self.arg) File "/home/runner/work/ipython/ipython/IPython/testing/decorators.py", line 223, in skipper_func return f(*args, **kwargs) File "/home/runner/work/ipython/ipython/IPython/core/tests/test_magic.py", line 953, in test_script_out ip.run_cell_magic("script", "--out output sh", "echo 'hi'") File "/home/runner/work/ipython/ipython/IPython/core/interactiveshell.py", line 2417, in run_cell_magic result = fn(*args, **kwargs) File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/decorator.py", line 232, in fun return caller(func, *(extras + args), **kw) File "/home/runner/work/ipython/ipython/IPython/core/magic.py", line 187, in call = lambda f, *a, **k: f(*a, **k) File "/home/runner/work/ipython/ipython/IPython/core/magics/script.py", line 216, in shebang stdin=asyncio.subprocess.PIPE, File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete return future.result() File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/asyncio/subprocess.py", line 217, in create_subprocess_exec stderr=stderr, **kwds) File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/asyncio/base_events.py", line 1544, in subprocess_exec bufsize, **kwargs) File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/asyncio/unix_events.py", line 193, in _make_subprocess_transport self._child_watcher_callback, transp) File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/asyncio/unix_events.py", line 941, in add_child_handler "Cannot add child handler, " RuntimeError: Cannot add child handler, the child watcher does not have a loop attached --- .github/workflows/python-package.yml | 1 + .github/workflows/test.yml | 2 +- IPython/core/magics/script.py | 53 ++++++++++++++++++++++------ IPython/core/tests/test_magic.py | 53 +++++++++++++++++++--------- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ef24d627813..36ae8187fe2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -13,6 +13,7 @@ jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 strategy: matrix: python-version: [3.8] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2788e8640a8..19a0ba05158 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,6 @@ jobs: cp /tmp/.coverage ./ - name: pytest run: | - pytest + pytest -v - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index d4e7b08c3c1..99258a87d96 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -3,24 +3,24 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio +import atexit import errno +import functools import os -import sys import signal +import sys import time -import asyncio -import atexit - +from asyncio import SafeChildWatcher +from contextlib import contextmanager from subprocess import CalledProcessError +from traitlets import Dict, List, default + from IPython.core import magic_arguments -from IPython.core.magic import ( - Magics, magics_class, line_magic, cell_magic -) +from IPython.core.magic import Magics, cell_magic, line_magic, magics_class from IPython.lib.backgroundjobs import BackgroundJobManager from IPython.utils.process import arg_split -from traitlets import List, Dict, default - #----------------------------------------------------------------------------- # Magic implementation classes @@ -67,6 +67,39 @@ def script_args(f): f = arg(f) return f + +@contextmanager +def safe_watcher(): + if sys.platform == "win32": + yield + return + + policy = asyncio.get_event_loop_policy() + old_watcher = policy.get_child_watcher() + if isinstance(old_watcher, SafeChildWatcher): + yield + return + + loop = asyncio.get_event_loop() + try: + watcher = asyncio.SafeChildWatcher() + watcher.attach_loop(loop) + policy.set_child_watcher(watcher) + yield + finally: + watcher.close() + policy.set_child_watcher(old_watcher) + + +def dec_safe_watcher(fun): + @functools.wraps(fun) + def _inner(*args, **kwargs): + with safe_watcher(): + return fun(*args, **kwargs) + + return _inner + + @magics_class class ScriptMagics(Magics): """Magics for talking to scripts @@ -157,6 +190,7 @@ def named_script_magic(line, cell): @magic_arguments.magic_arguments() @script_args @cell_magic("script") + @dec_safe_watcher def shebang(self, line, cell): """Run a cell via a shell command @@ -204,7 +238,6 @@ async def _stream_communicate(process, cell): if sys.platform.startswith("win"): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) loop = asyncio.get_event_loop() - argv = arg_split(line, posix=not sys.platform.startswith("win")) args, cmd = self.shebang.parser.parse_known_args(argv) try: diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index fd5696d686f..6ecbe61f387 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -4,38 +4,40 @@ Needs to be run by nose (to make ipython session available). """ +import asyncio import io import os import re +import shlex import sys import warnings -from textwrap import dedent -from unittest import TestCase -from unittest import mock from importlib import invalidate_caches from io import StringIO from pathlib import Path +from textwrap import dedent +from unittest import TestCase, mock import nose.tools as nt -import shlex - from IPython import get_ipython from IPython.core import magic from IPython.core.error import UsageError -from IPython.core.magic import (Magics, magics_class, line_magic, - cell_magic, - register_line_magic, register_cell_magic) -from IPython.core.magics import execution, script, code, logging, osm +from IPython.core.magic import ( + Magics, + cell_magic, + line_magic, + magics_class, + register_cell_magic, + register_line_magic, +) +from IPython.core.magics import code, execution, logging, osm, script from IPython.testing import decorators as dec from IPython.testing import tools as tt from IPython.utils.io import capture_output -from IPython.utils.tempdir import (TemporaryDirectory, - TemporaryWorkingDirectory) from IPython.utils.process import find_cmd -from .test_debugger import PdbTestInput +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory -import pytest +from .test_debugger import PdbTestInput @magic.magics_class @@ -947,32 +949,48 @@ def test_script_config(): sm = script.ScriptMagics(shell=ip) nt.assert_in('whoda', sm.magics['cell']) +@dec.skip_iptest_but_not_pytest @dec.skip_win32 def test_script_out(): + assert asyncio.get_event_loop().is_running() is False + ip = get_ipython() ip.run_cell_magic("script", "--out output sh", "echo 'hi'") + assert asyncio.get_event_loop().is_running() is False nt.assert_equal(ip.user_ns['output'], 'hi\n') +@dec.skip_iptest_but_not_pytest @dec.skip_win32 def test_script_err(): ip = get_ipython() + assert asyncio.get_event_loop().is_running() is False ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") + assert asyncio.get_event_loop().is_running() is False nt.assert_equal(ip.user_ns['error'], 'hello\n') + +@dec.skip_iptest_but_not_pytest @dec.skip_win32 def test_script_out_err(): + ip = get_ipython() - ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2") - nt.assert_equal(ip.user_ns['output'], 'hi\n') - nt.assert_equal(ip.user_ns['error'], 'hello\n') + ip.run_cell_magic( + "script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2" + ) + nt.assert_equal(ip.user_ns["output"], "hi\n") + nt.assert_equal(ip.user_ns["error"], "hello\n") + +@dec.skip_iptest_but_not_pytest @dec.skip_win32 async def test_script_bg_out(): ip = get_ipython() ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") nt.assert_equal((await ip.user_ns["output"].read()), b"hi\n") - ip.user_ns['output'].close() + ip.user_ns["output"].close() + asyncio.get_event_loop().stop() +@dec.skip_iptest_but_not_pytest @dec.skip_win32 async def test_script_bg_err(): ip = get_ipython() @@ -981,6 +999,7 @@ async def test_script_bg_err(): ip.user_ns["error"].close() +@dec.skip_iptest_but_not_pytest @dec.skip_win32 async def test_script_bg_out_err(): ip = get_ipython() From b93fe0c828c23d4b0c87fcf8fb70c9acd43d1603 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 17 Oct 2021 13:18:58 -0700 Subject: [PATCH 1567/3726] more agressive skip on windows --- IPython/core/tests/test_magic.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 6ecbe61f387..d534d11018b 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -19,6 +19,8 @@ import nose.tools as nt +import pytest + from IPython import get_ipython from IPython.core import magic from IPython.core.error import UsageError @@ -951,6 +953,9 @@ def test_script_config(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) def test_script_out(): assert asyncio.get_event_loop().is_running() is False @@ -961,6 +966,9 @@ def test_script_out(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) def test_script_err(): ip = get_ipython() assert asyncio.get_event_loop().is_running() is False @@ -971,6 +979,9 @@ def test_script_err(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) def test_script_out_err(): ip = get_ipython() @@ -983,6 +994,9 @@ def test_script_out_err(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) async def test_script_bg_out(): ip = get_ipython() ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") @@ -992,6 +1006,9 @@ async def test_script_bg_out(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) async def test_script_bg_err(): ip = get_ipython() ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") @@ -1001,6 +1018,9 @@ async def test_script_bg_err(): @dec.skip_iptest_but_not_pytest @dec.skip_win32 +@pytest.mark.skipif( + sys.platform == "win32", reason="This test does not run under Windows" +) async def test_script_bg_out_err(): ip = get_ipython() ip.run_cell_magic( From f7213ea2a130207450771054ea17fc7df45625f2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 17 Oct 2021 17:24:42 -0700 Subject: [PATCH 1568/3726] conditional windows import --- IPython/core/magics/script.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index 99258a87d96..5bf6a023e13 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -11,7 +11,6 @@ import signal import sys import time -from asyncio import SafeChildWatcher from contextlib import contextmanager from subprocess import CalledProcessError @@ -74,6 +73,8 @@ def safe_watcher(): yield return + from asyncio import SafeChildWatcher + policy = asyncio.get_event_loop_policy() old_watcher = policy.get_child_watcher() if isinstance(old_watcher, SafeChildWatcher): From 887df6c2966685672f5236dc7ece8b29800b38df Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 18 Oct 2021 12:51:17 +0200 Subject: [PATCH 1569/3726] match log message for profile by name and path we had debug-level for name, info-level for full path. Make both debug-level for consistency --- IPython/core/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/application.py b/IPython/core/application.py index db48ab2bb44..85c91274d4e 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -398,7 +398,7 @@ def init_profile_dir(self): self.log.fatal("Profile %r not found."%self.profile) self.exit(1) else: - self.log.debug("Using existing profile dir: %r"%p.location) + self.log.debug(f"Using existing profile dir: {p.location!r}") else: location = self.config.ProfileDir.location # location is fully specified @@ -418,7 +418,7 @@ def init_profile_dir(self): self.log.fatal("Profile directory %r not found."%location) self.exit(1) else: - self.log.info("Using existing profile dir: %r"%location) + self.log.debug(f"Using existing profile dir: {p.location!r}") # if profile_dir is specified explicitly, set profile name dir_name = os.path.basename(p.location) if dir_name.startswith('profile_'): From e8d652a21b5b829b8d494741bee11eff614622c6 Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Wed, 13 Oct 2021 15:43:35 +0200 Subject: [PATCH 1570/3726] Call inspect.signature on full class --- IPython/core/completer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 9237929f9a7..167acbd643c 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1542,7 +1542,7 @@ def _default_arguments(self, obj): inspect.Parameter.POSITIONAL_OR_KEYWORD) try: - sig = inspect.signature(call_obj) + sig = inspect.signature(obj) ret.extend(k for k, v in sig.parameters.items() if v.kind in _keeps) except ValueError: From be24009069749d2f01bec90b361b8f389f9c35f6 Mon Sep 17 00:00:00 2001 From: Meysam Azad Date: Sun, 17 Oct 2021 19:50:10 +0300 Subject: [PATCH 1571/3726] =?UTF-8?q?feat:=20remove=20deprecated=20code=20?= =?UTF-8?q?&=20files=20=E2=9A=B0=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IPython/__init__.py | 5 ----- IPython/extensions/cythonmagic.py | 21 ------------------- IPython/extensions/rmagic.py | 12 ----------- IPython/extensions/sympyprinting.py | 32 ----------------------------- 4 files changed, 70 deletions(-) delete mode 100644 IPython/extensions/cythonmagic.py delete mode 100644 IPython/extensions/rmagic.py delete mode 100644 IPython/extensions/sympyprinting.py diff --git a/IPython/__init__.py b/IPython/__init__.py index 0b182bd8400..7d098bc9ee7 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -41,11 +41,6 @@ """) -# Make it easy to import extensions - they are always directly on pythonpath. -# Therefore, non-IPython modules can be added to extensions directory. -# This should probably be in ipapp.py. -sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) - #----------------------------------------------------------------------------- # Setup the top level names #----------------------------------------------------------------------------- diff --git a/IPython/extensions/cythonmagic.py b/IPython/extensions/cythonmagic.py deleted file mode 100644 index 3c88e7c2a1c..00000000000 --- a/IPython/extensions/cythonmagic.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -""" -**DEPRECATED** - -The cython magic has been integrated into Cython itself, -which is now released in version 0.21. - -cf github `Cython` organisation, `Cython` repo, under the -file `Cython/Build/IpythonMagic.py` -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2010-2011, IPython Development Team. -#----------------------------------------------------------------------------- - -import warnings - -## still load the magic in IPython 3.x, remove completely in future versions. -def load_ipython_extension(ip): - """Load the extension in IPython.""" - - warnings.warn("""The Cython magic has been moved to the Cython package""") diff --git a/IPython/extensions/rmagic.py b/IPython/extensions/rmagic.py deleted file mode 100644 index ec5763972e4..00000000000 --- a/IPython/extensions/rmagic.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- - -#----------------------------------------------------------------------------- -# Copyright (C) 2012 The IPython Development Team -#----------------------------------------------------------------------------- - -import warnings - -def load_ipython_extension(ip): - """Load the extension in IPython.""" - warnings.warn("The rmagic extension in IPython has moved to " - "`rpy2.ipython`, please see `rpy2` documentation.") diff --git a/IPython/extensions/sympyprinting.py b/IPython/extensions/sympyprinting.py deleted file mode 100644 index e6a83cd34b6..00000000000 --- a/IPython/extensions/sympyprinting.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -**DEPRECATED** - -A print function that pretty prints sympy Basic objects. - -:moduleauthor: Brian Granger - -Usage -===== - -Once the extension is loaded, Sympy Basic objects are automatically -pretty-printed. - -As of SymPy 0.7.2, maintenance of this extension has moved to SymPy under -sympy.interactive.ipythonprinting, any modifications to account for changes to -SymPy should be submitted to SymPy rather than changed here. This module is -maintained here for backwards compatibility with old SymPy versions. - -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import warnings - -def load_ipython_extension(ip): - warnings.warn("The sympyprinting extension has moved to `sympy`, " - "use `from sympy import init_printing; init_printing()`") From bd9c17bf479482b5b6d98994c2710b0e1e2839c4 Mon Sep 17 00:00:00 2001 From: Meysam Azad Date: Sun, 17 Oct 2021 22:55:40 +0300 Subject: [PATCH 1572/3726] =?UTF-8?q?docs:=20add=20changelog=20notice=20?= =?UTF-8?q?=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/whatsnew/pr/remove-deprecated-stuff.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docs/source/whatsnew/pr/remove-deprecated-stuff.rst diff --git a/docs/source/whatsnew/pr/remove-deprecated-stuff.rst b/docs/source/whatsnew/pr/remove-deprecated-stuff.rst new file mode 100644 index 00000000000..2a948e4f2d1 --- /dev/null +++ b/docs/source/whatsnew/pr/remove-deprecated-stuff.rst @@ -0,0 +1,8 @@ +Remove Deprecated Stuff +================================ + +We no longer need to add `extensions` to the PYTHONPATH because that is being +handled by `load_extension`. + +We are also removing Cythonmagic, sympyprinting and rmagic as they are now in +other packages and no longer need to be inside IPython. From 37c1038b91bc3415f2bf0f43bb52138934a7e505 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 18 Oct 2021 15:28:03 -0700 Subject: [PATCH 1573/3726] hardcode builtins extensions --- IPython/core/extensions.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/IPython/core/extensions.py b/IPython/core/extensions.py index db3b040d24f..ce419e1bd40 100644 --- a/IPython/core/extensions.py +++ b/IPython/core/extensions.py @@ -19,6 +19,9 @@ # Main class #----------------------------------------------------------------------------- +BUILTINS_EXTS = {"storemagic": False, "autoreload": False} + + class ExtensionManager(Configurable): """A class to manage IPython extensions. @@ -62,13 +65,22 @@ def ipython_extension_dir(self): def _on_ipython_dir_changed(self, change): ensure_dir_exists(self.ipython_extension_dir) - def load_extension(self, module_str): + def load_extension(self, module_str: str): """Load an IPython extension by its module name. Returns the string "already loaded" if the extension is already loaded, "no load function" if the module doesn't have a load_ipython_extension function, or None if it succeeded. """ + try: + return self._load_extension(module_str) + except ModuleNotFoundError: + if module_str in BUILTINS_EXTS: + BUILTINS_EXTS[module_str] = True + return self._load_extension("IPython.extensions." + module_str) + raise + + def _load_extension(self, module_str: str): if module_str in self.loaded: return "already loaded" @@ -89,7 +101,7 @@ def load_extension(self, module_str): else: return "no load function" - def unload_extension(self, module_str): + def unload_extension(self, module_str: str): """Unload an IPython extension by its module name. This function looks up the extension's name in ``sys.modules`` and @@ -99,9 +111,11 @@ def unload_extension(self, module_str): a function to unload itself, "not loaded" if the extension isn't loaded, otherwise None. """ + if BUILTINS_EXTS.get(module_str, False) is True: + module_str = "IPython.extensions." + module_str if module_str not in self.loaded: return "not loaded" - + if module_str in sys.modules: mod = sys.modules[module_str] if self._call_unload_ipython_extension(mod): @@ -109,7 +123,7 @@ def unload_extension(self, module_str): else: return "no unload function" - def reload_extension(self, module_str): + def reload_extension(self, module_str: str): """Reload an IPython extension by calling reload. If the module has not been loaded before, @@ -119,6 +133,9 @@ def reload_extension(self, module_str): """ from IPython.utils.syspathcontext import prepended_to_syspath + if BUILTINS_EXTS.get(module_str, False) is True: + module_str = "IPython.extensions." + module_str + if (module_str in self.loaded) and (module_str in sys.modules): self.unload_extension(module_str) mod = sys.modules[module_str] From 17153999ccdd336213a1e2d85ffc4b66f402e595 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 20 Oct 2021 00:53:33 +0300 Subject: [PATCH 1574/3726] Fix unintentional skipping of module level doctests Importing `skip_doctest` decorator unintentionally marks for skipping a module level doctest. It happens because doctests discovery only checks whether a variable with name `skip_doctest` is presented without checking the type. I have renamed the 'magic' variable name to `__skip_doctest__` to resolve the name clash, and also made the check actually depend on the variable content. The module level doctest in `core/debugger.py` was previously unintentionally skipped and now is disabled because it contains syntax/name errors. --- IPython/core/completer.py | 2 +- IPython/core/debugger.py | 2 ++ IPython/extensions/autoreload.py | 2 +- IPython/testing/plugin/ipdoctest.py | 2 +- IPython/testing/skipdoctest.py | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 167acbd643c..8ec99f4d7a2 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -142,7 +142,7 @@ import __main__ # skip module docstests -skip_doctest = True +__skip_doctest__ = True try: import jedi diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 0083f537227..751145843c9 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -116,6 +116,8 @@ from IPython.core.excolors import exception_colors from IPython.testing.skipdoctest import skip_doctest +# skip module docstests +__skip_doctest__ = True prompt = 'ipdb> ' diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index f481520a4c5..bcf1a198021 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -97,7 +97,7 @@ - C extension modules cannot be reloaded, and so cannot be autoreloaded. """ -skip_doctest = True +__skip_doctest__ = True # ----------------------------------------------------------------------------- # Copyright (C) 2000 Thomas Heller diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 407aeb6f4f4..fdc8822b853 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -125,7 +125,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): add them to `tests`. """ print('_find for:', obj, name, module) # dbg - if hasattr(obj,"skip_doctest"): + if bool(getattr(obj, "__skip_doctest__", False)): #print 'SKIPPING DOCTEST FOR:',obj # dbg obj = DocTestSkip(obj) diff --git a/IPython/testing/skipdoctest.py b/IPython/testing/skipdoctest.py index b0cf83c449e..f440ea14b27 100644 --- a/IPython/testing/skipdoctest.py +++ b/IPython/testing/skipdoctest.py @@ -15,5 +15,5 @@ def skip_doctest(f): This decorator allows you to mark a function whose docstring you wish to omit from testing, while preserving the docstring for introspection, help, etc.""" - f.skip_doctest = True + f.__skip_doctest__ = True return f From 91936a9d1c432270c7273c090fc6857dcceafd1a Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:29:44 +0200 Subject: [PATCH 1575/3726] Do not use mutables as default arguments --- docs/sphinxext/github.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/sphinxext/github.py b/docs/sphinxext/github.py index a24b5a62e47..f56181e64e9 100644 --- a/docs/sphinxext/github.py +++ b/docs/sphinxext/github.py @@ -51,7 +51,7 @@ def make_link_node(rawtext, app, type, slug, options): **options) return node -def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghissue_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub issue. Returns 2 part tuple containing list of nodes to insert into the @@ -66,6 +66,8 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} try: issue_num = int(text) @@ -92,7 +94,7 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): node = make_link_node(rawtext, app, category, str(issue_num), options) return [node], [] -def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghuser_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub user. Returns 2 part tuple containing list of nodes to insert into the @@ -107,13 +109,16 @@ def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} + app = inliner.document.settings.env.app #info('user link %r' % text) ref = 'https://www.github.com/' + text node = nodes.reference(rawtext, text, refuri=ref, **options) return [node], [] -def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghcommit_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub commit. Returns 2 part tuple containing list of nodes to insert into the @@ -128,6 +133,9 @@ def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} + app = inliner.document.settings.env.app #info('user link %r' % text) try: From 5f90ef7cc824f6dd4b53583ef8d4d9fc91ba95d1 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Wed, 20 Oct 2021 23:05:55 +0200 Subject: [PATCH 1576/3726] Typos found by codespell --- IPython/core/completer.py | 8 ++++---- IPython/core/debugger.py | 14 +++++++------- IPython/core/tests/test_alias.py | 2 +- IPython/core/tests/test_completer.py | 4 ++-- IPython/core/tests/test_display.py | 2 +- IPython/core/tests/test_inputtransformer2.py | 2 +- IPython/core/tests/test_run.py | 2 +- IPython/extensions/tests/test_autoreload.py | 2 +- IPython/external/qt_loaders.py | 6 +++--- IPython/lib/backgroundjobs.py | 2 +- IPython/lib/demo.py | 2 +- IPython/testing/decorators.py | 2 +- IPython/utils/path.py | 2 +- examples/IPython Kernel/gui/gui-gtk.py | 2 +- examples/IPython Kernel/gui/gui-gtk3.py | 2 +- examples/IPython Kernel/gui/gui-gtk4.py | 2 +- tools/release_helper.sh | 4 ++-- tools/retar.py | 2 +- tools/tests/CSS Reference.ipynb | 2 +- 19 files changed, 32 insertions(+), 32 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 167acbd643c..497aa4f1475 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -157,8 +157,8 @@ #----------------------------------------------------------------------------- # ranges where we have most of the valid unicode names. We could be more finer -# grained but is it worth it for performace While unicode have character in the -# rage 0, 0x110000, we seem to have name for about 10% of those. (131808 as I +# grained but is it worth it for performance While unicode have character in the +# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I # write this). With below range we cover them all, with a density of ~67% # biggest next gap we consider only adds up about 1% density and there are 600 # gaps that would need hard coding. @@ -2062,7 +2062,7 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, A tuple of N elements which are (likely): matched_text: ? the text that the complete matched matches: list of completions ? - matches_origin: ? list same lenght as matches, and where each completion came from + matches_origin: ? list same length as matches, and where each completion came from jedi_matches: list of Jedi matches, have it's own structure. """ @@ -2179,7 +2179,7 @@ def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. # We could do a faster match using a Trie. - # Using pygtrie the follwing seem to work: + # Using pygtrie the following seem to work: # s = PrefixSet() diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 0083f537227..5d3139e9c41 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -69,8 +69,8 @@ -Licencse --------- +License +------- Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't @@ -427,7 +427,7 @@ def hidden_frames(self, stack): # The f_locals dictionary is updated from the actual frame # locals whenever the .f_locals accessor is called, so we # avoid calling it here to preserve self.curframe_locals. - # Futhermore, there is no good reason to hide the current frame. + # Furthermore, there is no good reason to hide the current frame. ip_hide = [self._hidden_predicate(s[0]) for s in stack] ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] if ip_start and self._predicates["ipython_internal"]: @@ -520,8 +520,8 @@ def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', def _get_frame_locals(self, frame): """ " - Acessing f_local of current frame reset the namespace, so we want to avoid - that or the following can happend + Accessing f_local of current frame reset the namespace, so we want to avoid + that or the following can happen ipdb> foo "old" @@ -993,7 +993,7 @@ def do_up(self, arg): if counter >= count: break else: - # if no break occured. + # if no break occurred. self.error( "all frames above hidden, use `skip_hidden False` to get get into those." ) @@ -1038,7 +1038,7 @@ def do_down(self, arg): break else: self.error( - "all frames bellow hidden, use `skip_hidden False` to get get into those." + "all frames below hidden, use `skip_hidden False` to get get into those." ) return diff --git a/IPython/core/tests/test_alias.py b/IPython/core/tests/test_alias.py index 90971deecb3..a84b0095334 100644 --- a/IPython/core/tests/test_alias.py +++ b/IPython/core/tests/test_alias.py @@ -49,7 +49,7 @@ def test_alias_args_commented(): _ip.run_cell('commetarg') # strip() is for pytest compat; testing via iptest patch IPython shell - # in testin.globalipapp and replace the system call which messed up the + # in testing.globalipapp and replace the system call which messed up the # \r\n assert cap.stdout.strip() == 'this is %s a commented out arg' diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index ad5a080ec9d..689b93e6cce 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -86,7 +86,7 @@ def test_unicode_range(): size, start, stop, prct = recompute_unicode_ranges() message = f"""_UNICODE_RANGES likely wrong and need updating. This is likely due to a new release of Python. We've find that the biggest gap - in unicode characters has reduces in size to be {size} charaters + in unicode characters has reduces in size to be {size} characters ({prct}), from {start}, to {stop}. In completer.py likely update to _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)] @@ -302,7 +302,7 @@ def test_latex_no_results(self): def test_back_latex_completion(self): ip = get_ipython() - # do not return more than 1 matches fro \beta, only the latex one. + # do not return more than 1 matches for \beta, only the latex one. name, matches = ip.complete("\\β") nt.assert_equal(matches, ['\\beta']) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index 4b783bc50ae..15cae218587 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -125,7 +125,7 @@ def test_image_filename_defaults(): embed=True) nt.assert_raises(ValueError, display.Image) nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True) - # check boths paths to allow packages to test at build and install time + # check both paths to allow packages to test at build and install time imgfile = os.path.join(tpath, 'core/tests/2x2.png') img = display.Image(filename=imgfile) nt.assert_equal('png', img.format) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index e024d7cc923..cd7c5311e45 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -300,7 +300,7 @@ def test_check_complete_II(): def test_check_complete_invalidates_sunken_brackets(): """ Test that a single line with more closing brackets than the opening ones is - interpretted as invalid + interpreted as invalid """ cc = ipt2.TransformerManager().check_complete nt.assert_equal(cc(")"), ("invalid", None)) diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 9634913ba76..5b690645af9 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -557,7 +557,7 @@ def test_multiprocessing_run(): """Set we can run mutiprocesgin without messing up up main namespace Note that import `nose.tools as nt` mdify the value s - sys.module['__mp_main__'] so wee need to temporarily set it to None to test + sys.module['__mp_main__'] so we need to temporarily set it to None to test the issue. """ with TemporaryDirectory() as td: diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index a84c8c9fe96..4bccd4068e0 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -303,7 +303,7 @@ class TestEnum(Enum): self.shell.run_code("t = Test()") # test global built-in var now exists self.shell.run_code("number") - # test the enumerations gets loaded succesfully + # test the enumerations gets loaded successfully self.shell.run_code("TestEnum.A") # ----------- TEST NEW OBJ CAN BE CHANGED -------------------- diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 79805358e72..982590e6b10 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -198,7 +198,7 @@ def import_pyqt4(version=2): Which QString/QVariant API to use. Set to None to use the system default - ImportErrors rasied within this function are non-recoverable + ImportErrors raised within this function are non-recoverable """ # The new-style string API (version=2) automatically # converts QStrings to Unicode Python strings. Also, automatically unpacks @@ -229,7 +229,7 @@ def import_pyqt5(): """ Import PyQt5 - ImportErrors rasied within this function are non-recoverable + ImportErrors raised within this function are non-recoverable """ from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui @@ -251,7 +251,7 @@ def import_pyqt6(): """ Import PyQt6 - ImportErrors rasied within this function are non-recoverable + ImportErrors raised within this function are non-recoverable """ from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui diff --git a/IPython/lib/backgroundjobs.py b/IPython/lib/backgroundjobs.py index 31997e13f28..588c15b17f6 100644 --- a/IPython/lib/backgroundjobs.py +++ b/IPython/lib/backgroundjobs.py @@ -135,7 +135,7 @@ def new(self, func_or_exp, *args, **kwargs): job_manager.new(myfunc, x, y, kw=dict(z=1)) - The reason for this assymmetry is that the new() method needs to + The reason for this asymmetry is that the new() method needs to maintain access to its own keywords, and this prevents name collisions between arguments to new() and arguments to your own functions. diff --git a/IPython/lib/demo.py b/IPython/lib/demo.py index bfef82fc7dc..8367a6fd951 100644 --- a/IPython/lib/demo.py +++ b/IPython/lib/demo.py @@ -532,7 +532,7 @@ def highlight(self, block): elif token[0] == Token.Comment.Single: toks.append((Token.Comment.Single, token[1][0])) # parse comment content by rst lexer - # remove the extrat newline added by rst lexer + # remove the extra newline added by rst lexer toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1] else: toks.append(token) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index e31a47b1b88..a2458ce13fa 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -156,7 +156,7 @@ def decor(f): def skip_iptest_but_not_pytest(f): """ - Warnign this will make the test invisible to iptest. + Warning this will make the test invisible to iptest. """ import os diff --git a/IPython/utils/path.py b/IPython/utils/path.py index cc10dffd1f0..b347e98ed46 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -209,7 +209,7 @@ def get_home_dir(require_writable=False) -> str: pass if (not require_writable) or _writable_dir(homedir): - assert isinstance(homedir, str), "Homedir shoudl be unicode not bytes" + assert isinstance(homedir, str), "Homedir should be unicode not bytes" return homedir else: raise HomeDirError('%s is not a writable dir, ' diff --git a/examples/IPython Kernel/gui/gui-gtk.py b/examples/IPython Kernel/gui/gui-gtk.py index 80d888aff8b..3df9922b34b 100755 --- a/examples/IPython Kernel/gui/gui-gtk.py +++ b/examples/IPython Kernel/gui/gui-gtk.py @@ -13,7 +13,7 @@ import gtk -def hello_world(wigdet, data=None): +def hello_world(widget, data=None): print("Hello World") def delete_event(widget, event, data=None): diff --git a/examples/IPython Kernel/gui/gui-gtk3.py b/examples/IPython Kernel/gui/gui-gtk3.py index 935c026d5e3..f35f498d33b 100644 --- a/examples/IPython Kernel/gui/gui-gtk3.py +++ b/examples/IPython Kernel/gui/gui-gtk3.py @@ -11,7 +11,7 @@ from gi.repository import Gtk -def hello_world(wigdet, data=None): +def hello_world(widget, data=None): print("Hello World") def delete_event(widget, event, data=None): diff --git a/examples/IPython Kernel/gui/gui-gtk4.py b/examples/IPython Kernel/gui/gui-gtk4.py index bb8c56bf115..44ff1d97ed4 100644 --- a/examples/IPython Kernel/gui/gui-gtk4.py +++ b/examples/IPython Kernel/gui/gui-gtk4.py @@ -14,7 +14,7 @@ from gi.repository import Gtk, GLib # noqa -def hello_world(wigdet, data=None): +def hello_world(widget, data=None): print("Hello World") diff --git a/tools/release_helper.sh b/tools/release_helper.sh index ee7c06b3462..1c1d5e6cdb1 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -62,7 +62,7 @@ maybe_edit(){ echo -if ask_section "Updating what's new with informations from docs/source/whatsnew/pr" +if ask_section "Updating what's new with information from docs/source/whatsnew/pr" then python tools/update_whatsnew.py @@ -100,7 +100,7 @@ then git checkout $PREV_RELEASE echo $BLUE"Saving API to file $PREV_RELEASE"$NOR frappuccino IPython --save IPython-$PREV_RELEASE.json - echo $BLUE"comming back to $BRANCH"$NOR + echo $BLUE"coming back to $BRANCH"$NOR git checkout $BRANCH echo $BLUE"comparing ..."$NOR frappuccino IPython --compare IPython-$PREV_RELEASE.json diff --git a/tools/retar.py b/tools/retar.py index 3615e7410fc..b81345d280d 100644 --- a/tools/retar.py +++ b/tools/retar.py @@ -9,7 +9,7 @@ The process of creating an sdist can be non-reproducible: - directory created during the process get a mtime of the creation date; - - gziping files embed the timestamp of fo zip creation. + - gziping files embed the timestamp of zip creation. This will untar-retar; ensuring that all mtime > SOURCE_DATE_EPOCH will be set equal to SOURCE_DATE_EPOCH. diff --git a/tools/tests/CSS Reference.ipynb b/tools/tests/CSS Reference.ipynb index a9e0c74da7d..a567cc6ad0d 100644 --- a/tools/tests/CSS Reference.ipynb +++ b/tools/tests/CSS Reference.ipynb @@ -8144,7 +8144,7 @@ " \n", " \n", " \n", - " but thi\n", + " but this\n", " is the \n", " \n", " \n", From 24d76d5b45b534da8a14ba4848b7ad92047fc358 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 20 Oct 2021 19:52:14 -0700 Subject: [PATCH 1577/3726] Try to enable codecov status Not sure why it does not work. --- codecov.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/codecov.yml b/codecov.yml index eb9b9dff30c..8015ac1d6fc 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,5 @@ coverage: project: default: target: auto - threshold: 10 - patch: - default: - target: 0% +codecov: + require_ci_to_pass: false From ebeba47f2bcd473f4e76b22de2bd5b893abe9b6f Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:09:43 +0200 Subject: [PATCH 1578/3726] [setup] Add pytest to test dependencies --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0696e740e2f..6c30c9f4043 100755 --- a/setup.py +++ b/setup.py @@ -176,6 +176,7 @@ doc=["Sphinx>=1.3"], test=[ "nose>=0.10.1", + "pytest", "requests", "testpath", "pygments", From c8142425528cafc0352566ba2873db6b1d6ab10c Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:10:12 +0200 Subject: [PATCH 1579/3726] [pre-commit] Add initial configuration --- .pre-commit-config.yaml | 16 ++++++++++++++++ MANIFEST.in | 1 + 2 files changed, 17 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..6af0afb1d23 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/akaihola/darker + rev: 1.3.1 + hooks: + - id: darker + diff --git a/MANIFEST.in b/MANIFEST.in index ec33beb19f0..8740db350f6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,6 +8,7 @@ include pytest.ini include mypy.ini include .mailmap include .flake8 +include .pre-commit-config.yaml recursive-exclude tools * exclude tools From cf20712b7068ca94678c9c782074c7f858b53ccb Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:43:24 +0200 Subject: [PATCH 1580/3726] [testing][tests][decorators] Remove nose --- IPython/testing/tests/test_decorators.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/IPython/testing/tests/test_decorators.py b/IPython/testing/tests/test_decorators.py index dc1ff41d7da..eb93ea17600 100644 --- a/IPython/testing/tests/test_decorators.py +++ b/IPython/testing/tests/test_decorators.py @@ -6,9 +6,6 @@ import inspect import sys -# Third party -import nose.tools as nt - # Our own from IPython.testing import decorators as dec @@ -93,7 +90,7 @@ def test_skip_dt_decorator(): # Fetch the docstring from doctest_bad after decoration. val = doctest_bad.__doc__ - nt.assert_equal(check,val,"doctest_bad docstrings don't match") + assert check == val, "doctest_bad docstrings don't match" # Doctest skipping should work for class methods too @@ -152,13 +149,14 @@ def test_skip_dt_decorator2(): @dec.skip_linux def test_linux(): - nt.assert_false(sys.platform.startswith('linux'),"This test can't run under linux") + assert sys.platform.startswith("linux") is False, "This test can't run under linux" + @dec.skip_win32 def test_win32(): - nt.assert_not_equal(sys.platform,'win32',"This test can't run under windows") + assert sys.platform != "win32", "This test can't run under windows" + @dec.skip_osx def test_osx(): - nt.assert_not_equal(sys.platform,'darwin',"This test can't run under osx") - + assert sys.platform != "darwin", "This test can't run under osx" From 6b1a3e40a3cbbaaa8a442610414b36fad5aae07f Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:50:42 +0200 Subject: [PATCH 1581/3726] [testing][tests][tools] Remove nose --- IPython/testing/tests/test_tools.py | 40 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/IPython/testing/tests/test_tools.py b/IPython/testing/tests/test_tools.py index 33c65dfaca4..f9f7e716cbd 100644 --- a/IPython/testing/tests/test_tools.py +++ b/IPython/testing/tests/test_tools.py @@ -17,8 +17,6 @@ import os import unittest -import nose.tools as nt - from IPython.testing import decorators as dec from IPython.testing import tools as tt @@ -28,26 +26,26 @@ @dec.skip_win32 def test_full_path_posix(): - spath = '/foo/bar.py' - result = tt.full_path(spath,['a.txt','b.txt']) - nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt']) - spath = '/foo' - result = tt.full_path(spath,['a.txt','b.txt']) - nt.assert_equal(result, ['/a.txt', '/b.txt']) - result = tt.full_path(spath,'a.txt') - nt.assert_equal(result, ['/a.txt']) + spath = "/foo/bar.py" + result = tt.full_path(spath, ["a.txt", "b.txt"]) + assert result, ["/foo/a.txt" == "/foo/b.txt"] + spath = "/foo" + result = tt.full_path(spath, ["a.txt", "b.txt"]) + assert result, ["/a.txt" == "/b.txt"] + result = tt.full_path(spath, "a.txt") + assert result == ["/a.txt"] @dec.skip_if_not_win32 def test_full_path_win32(): - spath = 'c:\\foo\\bar.py' - result = tt.full_path(spath,['a.txt','b.txt']) - nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt']) - spath = 'c:\\foo' - result = tt.full_path(spath,['a.txt','b.txt']) - nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt']) - result = tt.full_path(spath,'a.txt') - nt.assert_equal(result, ['c:\\a.txt']) + spath = "c:\\foo\\bar.py" + result = tt.full_path(spath, ["a.txt", "b.txt"]) + assert result, ["c:\\foo\\a.txt" == "c:\\foo\\b.txt"] + spath = "c:\\foo" + result = tt.full_path(spath, ["a.txt", "b.txt"]) + assert result, ["c:\\a.txt" == "c:\\b.txt"] + result = tt.full_path(spath, "a.txt") + assert result == ["c:\\a.txt"] def test_parser(): @@ -56,8 +54,8 @@ def test_parser(): both = ("FAILED (errors=1, failures=1)", 1, 1) for txt, nerr, nfail in [err, fail, both]: nerr1, nfail1 = tt.parse_test_output(txt) - nt.assert_equal(nerr, nerr1) - nt.assert_equal(nfail, nfail1) + assert nerr == nerr1 + assert nfail == nfail1 def test_temp_pyfile(): @@ -66,7 +64,7 @@ def test_temp_pyfile(): assert os.path.isfile(fname) with open(fname) as fh2: src2 = fh2.read() - nt.assert_equal(src2, src) + assert src2 == src class TestAssertPrints(unittest.TestCase): def test_passing(self): From 1efb0510ba66b8dfd66b6ec42b2429f6aa67b45a Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Wed, 29 Sep 2021 20:32:57 +0200 Subject: [PATCH 1582/3726] [core][tests][application] Remove nose --- IPython/core/tests/test_application.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/IPython/core/tests/test_application.py b/IPython/core/tests/test_application.py index 45d42727c9b..891908e98e7 100644 --- a/IPython/core/tests/test_application.py +++ b/IPython/core/tests/test_application.py @@ -4,8 +4,6 @@ import os import tempfile -import nose.tools as nt - from traitlets import Unicode from IPython.core.application import BaseIPythonApplication @@ -65,9 +63,8 @@ class TestApp(BaseIPythonApplication): f.write("c.TestApp.test = 'config file'") app = TestApp() - app.initialize(['--profile-dir', td]) - nt.assert_equal(app.test, 'config file') + app.initialize(["--profile-dir", td]) + assert app.test == "config file" app = TestApp() - app.initialize(['--profile-dir', td, '--TestApp.test=cli']) - nt.assert_equal(app.test, 'cli') - + app.initialize(["--profile-dir", td, "--TestApp.test=cli"]) + assert app.test == "cli" From 4ac9d7f384bd6ccb41d9f3573409d27e4d9220d8 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:52:34 +0200 Subject: [PATCH 1583/3726] [core][tests][async_helpers] Remove nose --- IPython/core/tests/test_async_helpers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 3498b06be5f..9ca67f47e9e 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -4,7 +4,6 @@ Should only trigger on python 3.5+ or will have syntax errors. """ from itertools import chain, repeat -import nose.tools as nt from textwrap import dedent, indent from unittest import TestCase from IPython.testing.decorators import skip_without @@ -24,10 +23,10 @@ class AsyncTest(TestCase): def test_should_be_async(self): - nt.assert_false(_should_be_async("False")) - nt.assert_true(_should_be_async("await bar()")) - nt.assert_true(_should_be_async("x = await bar()")) - nt.assert_false( + self.assertFalse(_should_be_async("False")) + self.assertTrue(_should_be_async("await bar()")) + self.assertTrue(_should_be_async("x = await bar()")) + self.assertFalse( _should_be_async( dedent( """ @@ -298,7 +297,7 @@ def test_autoawait_trio_wrong_sleep(self): import asyncio await asyncio.sleep(0) """) - with nt.assert_raises(TypeError): + with self.assertRaises(TypeError): res.raise_error() @skip_without('trio') @@ -308,7 +307,7 @@ def test_autoawait_asyncio_wrong_sleep(self): import trio await trio.sleep(0) """) - with nt.assert_raises(RuntimeError): + with self.assertRaises(RuntimeError): res.raise_error() From 322441cbd2d1974954436631262b063d8f16d22f Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:56:23 +0200 Subject: [PATCH 1584/3726] [core][tests][completer] Remove nose --- IPython/core/tests/test_completer.py | 398 +++++++++++++-------------- 1 file changed, 197 insertions(+), 201 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 689b93e6cce..319bc2da08e 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -11,8 +11,6 @@ from contextlib import contextmanager -import nose.tools as nt - from traitlets.config.loader import Config from IPython import get_ipython from IPython.core import completer @@ -27,7 +25,6 @@ match_dict_keys, _deduplicate_completions, ) -from nose.tools import assert_in, assert_not_in # ----------------------------------------------------------------------------- # Test functions @@ -150,7 +147,7 @@ def test_protect_filename(): # run the actual tests for s1, s2 in pairs: s1p = completer.protect_filename(s1) - nt.assert_equal(s1p, s2) + assert s1p == s2 def check_line_split(splitter, test_specs): @@ -158,7 +155,7 @@ def check_line_split(splitter, test_specs): cursor_pos = len(part1) line = part1 + part2 out = splitter.split_line(line, cursor_pos) - nt.assert_equal(out, split) + assert out == split def test_line_split(): @@ -267,8 +264,8 @@ def test_unicode_completions(self): # should be thrown and the return value should be a pair of text, list # values. text, matches = ip.complete(t) - nt.assert_true(isinstance(text, str)) - nt.assert_true(isinstance(matches, list)) + self.assertIsInstance(text, str) + self.assertIsInstance(matches, list) def test_latex_completions(self): from IPython.core.latex_symbols import latex_symbols @@ -279,16 +276,16 @@ def test_latex_completions(self): keys = random.sample(latex_symbols.keys(), 10) for k in keys: text, matches = ip.complete(k) - nt.assert_equal(text, k) - nt.assert_equal(matches, [latex_symbols[k]]) + self.assertEqual(text, k) + self.assertEqual(matches, [latex_symbols[k]]) # Test a more complex line text, matches = ip.complete("print(\\alpha") - nt.assert_equal(text, "\\alpha") - nt.assert_equal(matches[0], latex_symbols["\\alpha"]) + self.assertEqual(text, "\\alpha") + self.assertEqual(matches[0], latex_symbols["\\alpha"]) # Test multiple matching latex symbols text, matches = ip.complete("\\al") - nt.assert_in("\\alpha", matches) - nt.assert_in("\\aleph", matches) + self.assertIn("\\alpha", matches) + self.assertIn("\\aleph", matches) def test_latex_no_results(self): """ @@ -296,30 +293,30 @@ def test_latex_no_results(self): """ ip = get_ipython() text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing") - nt.assert_equal(text, "") - nt.assert_equal(matches, ()) + self.assertEqual(text, "") + self.assertEqual(matches, ()) def test_back_latex_completion(self): ip = get_ipython() # do not return more than 1 matches for \beta, only the latex one. name, matches = ip.complete("\\β") - nt.assert_equal(matches, ['\\beta']) + self.assertEqual(matches, ["\\beta"]) def test_back_unicode_completion(self): ip = get_ipython() name, matches = ip.complete("\\Ⅴ") - nt.assert_equal(matches, ("\\ROMAN NUMERAL FIVE",)) + self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",)) def test_forward_unicode_completion(self): ip = get_ipython() name, matches = ip.complete("\\ROMAN NUMERAL FIVE") - nt.assert_equal(matches, ["Ⅴ"] ) # This is not a V - nt.assert_equal(matches, ["\u2164"] ) # same as above but explicit. + self.assertEqual(matches, ["Ⅴ"]) # This is not a V + self.assertEqual(matches, ["\u2164"]) # same as above but explicit. - @nt.nottest # now we have a completion for \jmath + @unittest.skip("now we have a completion for \jmath") @decorators.knownfailureif( sys.platform == "win32", "Fails if there is a C:\\j... path" ) @@ -329,7 +326,7 @@ def test_no_ascii_back_completion(self): # single ascii letter that don't have yet completions for letter in "jJ": name, matches = ip.complete("\\" + letter) - nt.assert_equal(matches, []) + self.assertEqual(matches, []) class CompletionSplitterTestCase(unittest.TestCase): def setUp(self): @@ -337,8 +334,8 @@ def setUp(self): def test_delim_setting(self): self.sp.delims = " " - nt.assert_equal(self.sp.delims, " ") - nt.assert_equal(self.sp._delim_expr, r"[\ ]") + self.assertEqual(self.sp.delims, " ") + self.assertEqual(self.sp._delim_expr, r"[\ ]") def test_spaces(self): """Test with only spaces as split chars.""" @@ -348,19 +345,19 @@ def test_spaces(self): def test_has_open_quotes1(self): for s in ["'", "'''", "'hi' '"]: - nt.assert_equal(completer.has_open_quotes(s), "'") + self.assertEqual(completer.has_open_quotes(s), "'") def test_has_open_quotes2(self): for s in ['"', '"""', '"hi" "']: - nt.assert_equal(completer.has_open_quotes(s), '"') + self.assertEqual(completer.has_open_quotes(s), '"') def test_has_open_quotes3(self): for s in ["''", "''' '''", "'hi' 'ipython'"]: - nt.assert_false(completer.has_open_quotes(s)) + self.assertFalse(completer.has_open_quotes(s)) def test_has_open_quotes4(self): for s in ['""', '""" """', '"hi" "ipython"']: - nt.assert_false(completer.has_open_quotes(s)) + self.assertFalse(completer.has_open_quotes(s)) @decorators.knownfailureif( sys.platform == "win32", "abspath completions fail on Windows" @@ -376,13 +373,13 @@ def test_abspath_file_completions(self): # Check simple completion c = ip.complete(prefix)[1] - nt.assert_equal(c, names) + self.assertEqual(c, names) # Now check with a function call cmd = 'a = f("%s' % prefix c = ip.complete(prefix, cmd)[1] comp = [prefix + s for s in suffixes] - nt.assert_equal(c, comp) + self.assertEqual(c, comp) def test_local_file_completions(self): ip = get_ipython() @@ -395,13 +392,13 @@ def test_local_file_completions(self): # Check simple completion c = ip.complete(prefix)[1] - nt.assert_equal(c, names) + self.assertEqual(c, names) # Now check with a function call cmd = 'a = f("%s' % prefix c = ip.complete(prefix, cmd)[1] comp = {prefix + s for s in suffixes} - nt.assert_true(comp.issubset(set(c))) + self.assertTrue(comp.issubset(set(c))) def test_quoted_file_completions(self): ip = get_ipython() @@ -417,21 +414,21 @@ def test_quoted_file_completions(self): c = ip.Completer._complete( cursor_line=0, cursor_pos=len(text), full_text=text )[1] - nt.assert_equal(c, [escaped]) + self.assertEqual(c, [escaped]) # Double quote requires no escape text = 'open("foo' c = ip.Completer._complete( cursor_line=0, cursor_pos=len(text), full_text=text )[1] - nt.assert_equal(c, [name]) + self.assertEqual(c, [name]) # No quote requires an escape text = "%ls foo" c = ip.Completer._complete( cursor_line=0, cursor_pos=len(text), full_text=text )[1] - nt.assert_equal(c, [escaped]) + self.assertEqual(c, [escaped]) def test_all_completions_dups(self): """ @@ -537,18 +534,18 @@ def test_greedy_completions(self): ip = get_ipython() ip.ex("a=list(range(5))") _, c = ip.complete(".", line="a[0].") - nt.assert_false(".real" in c, "Shouldn't have completed on a[0]: %s" % c) + self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c) def _(line, cursor_pos, expect, message, completion): with greedy_completion(), provisionalcompleter(): ip.Completer.use_jedi = False _, c = ip.complete(".", line=line, cursor_pos=cursor_pos) - nt.assert_in(expect, c, message % c) + self.assertIn(expect, c, message % c) ip.Completer.use_jedi = True with provisionalcompleter(): completions = ip.Completer.completions(line, cursor_pos) - nt.assert_in(completion, completions) + self.assertIn(completion, completions) with provisionalcompleter(): yield _, "a[0].", 5, "a[0].real", "Should have completed on a[0].: %s", Completion( @@ -575,13 +572,13 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - nt.assert_in("ip.__str__", matches) - nt.assert_in("ip._hidden_attr", matches) + self.assertIn("ip.__str__", matches) + self.assertIn("ip._hidden_attr", matches) # c.use_jedi = True # completions = set(c.completions('ip.', 3)) - # nt.assert_in(Completion(3, 3, '__str__'), completions) - # nt.assert_in(Completion(3,3, "_hidden_attr"), completions) + # self.assertIn(Completion(3, 3, '__str__'), completions) + # self.assertIn(Completion(3,3, "_hidden_attr"), completions) cfg = Config() cfg.IPCompleter.omit__names = 1 @@ -589,13 +586,13 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - nt.assert_not_in("ip.__str__", matches) - # nt.assert_in('ip._hidden_attr', matches) + self.assertNotIn("ip.__str__", matches) + # self.assertIn('ip._hidden_attr', matches) # c.use_jedi = True # completions = set(c.completions('ip.', 3)) - # nt.assert_not_in(Completion(3,3,'__str__'), completions) - # nt.assert_in(Completion(3,3, "_hidden_attr"), completions) + # self.assertNotIn(Completion(3,3,'__str__'), completions) + # self.assertIn(Completion(3,3, "_hidden_attr"), completions) cfg = Config() cfg.IPCompleter.omit__names = 2 @@ -603,22 +600,22 @@ def test_omit__names(self): with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip.") - nt.assert_not_in("ip.__str__", matches) - nt.assert_not_in("ip._hidden_attr", matches) + self.assertNotIn("ip.__str__", matches) + self.assertNotIn("ip._hidden_attr", matches) # c.use_jedi = True # completions = set(c.completions('ip.', 3)) - # nt.assert_not_in(Completion(3,3,'__str__'), completions) - # nt.assert_not_in(Completion(3,3, "_hidden_attr"), completions) + # self.assertNotIn(Completion(3,3,'__str__'), completions) + # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions) with provisionalcompleter(): c.use_jedi = False s, matches = c.complete("ip._x.") - nt.assert_in("ip._x.keys", matches) + self.assertIn("ip._x.keys", matches) # c.use_jedi = True # completions = set(c.completions('ip._x.', 6)) - # nt.assert_in(Completion(6,6, "keys"), completions) + # self.assertIn(Completion(6,6, "keys"), completions) del ip._hidden_attr del ip._x @@ -636,21 +633,21 @@ def test_limit_to__all__False_ok(self): cfg.IPCompleter.limit_to__all__ = False c.update_config(cfg) s, matches = c.complete("d.") - nt.assert_in("d.x", matches) + self.assertIn("d.x", matches) def test_get__all__entries_ok(self): class A: __all__ = ["x", 1] words = completer.get__all__entries(A()) - nt.assert_equal(words, ["x"]) + self.assertEqual(words, ["x"]) def test_get__all__entries_no__all__ok(self): class A: pass words = completer.get__all__entries(A()) - nt.assert_equal(words, []) + self.assertEqual(words, []) def test_func_kw_completions(self): ip = get_ipython() @@ -658,39 +655,39 @@ def test_func_kw_completions(self): c.use_jedi = False ip.ex("def myfunc(a=1,b=2): return a+b") s, matches = c.complete(None, "myfunc(1,b") - nt.assert_in("b=", matches) + self.assertIn("b=", matches) # Simulate completing with cursor right after b (pos==10): s, matches = c.complete(None, "myfunc(1,b)", 10) - nt.assert_in("b=", matches) + self.assertIn("b=", matches) s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') - nt.assert_in("b=", matches) + self.assertIn("b=", matches) # builtin function s, matches = c.complete(None, "min(k, k") - nt.assert_in("key=", matches) + self.assertIn("key=", matches) def test_default_arguments_from_docstring(self): ip = get_ipython() c = ip.Completer kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value") - nt.assert_equal(kwd, ["key"]) + self.assertEqual(kwd, ["key"]) # with cython type etc kwd = c._default_arguments_from_docstring( "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" ) - nt.assert_equal(kwd, ["ncall", "resume", "nsplit"]) + self.assertEqual(kwd, ["ncall", "resume", "nsplit"]) # white spaces kwd = c._default_arguments_from_docstring( "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" ) - nt.assert_equal(kwd, ["ncall", "resume", "nsplit"]) + self.assertEqual(kwd, ["ncall", "resume", "nsplit"]) def test_line_magics(self): ip = get_ipython() c = ip.Completer s, matches = c.complete(None, "lsmag") - nt.assert_in("%lsmagic", matches) + self.assertIn("%lsmagic", matches) s, matches = c.complete(None, "%lsmag") - nt.assert_in("%lsmagic", matches) + self.assertIn("%lsmagic", matches) def test_cell_magics(self): from IPython.core.magic import register_cell_magic @@ -703,9 +700,9 @@ def _foo_cellm(line, cell): c = ip.Completer s, matches = c.complete(None, "_foo_ce") - nt.assert_in("%%_foo_cellm", matches) + self.assertIn("%%_foo_cellm", matches) s, matches = c.complete(None, "%%_foo_ce") - nt.assert_in("%%_foo_cellm", matches) + self.assertIn("%%_foo_cellm", matches) def test_line_cell_magics(self): from IPython.core.magic import register_line_cell_magic @@ -722,14 +719,14 @@ def _bar_cellm(line, cell): # and this will show a difference if the same name is both a line and cell # magic. s, matches = c.complete(None, "_bar_ce") - nt.assert_in("%_bar_cellm", matches) - nt.assert_in("%%_bar_cellm", matches) + self.assertIn("%_bar_cellm", matches) + self.assertIn("%%_bar_cellm", matches) s, matches = c.complete(None, "%_bar_ce") - nt.assert_in("%_bar_cellm", matches) - nt.assert_in("%%_bar_cellm", matches) + self.assertIn("%_bar_cellm", matches) + self.assertIn("%%_bar_cellm", matches) s, matches = c.complete(None, "%%_bar_ce") - nt.assert_not_in("%_bar_cellm", matches) - nt.assert_in("%%_bar_cellm", matches) + self.assertNotIn("%_bar_cellm", matches) + self.assertIn("%%_bar_cellm", matches) def test_magic_completion_order(self): ip = get_ipython() @@ -737,7 +734,7 @@ def test_magic_completion_order(self): # Test ordering of line and cell magics. text, matches = c.complete("timeit") - nt.assert_equal(matches, ["%timeit", "%%timeit"]) + self.assertEqual(matches, ["%timeit", "%%timeit"]) def test_magic_completion_shadowing(self): ip = get_ipython() @@ -746,18 +743,18 @@ def test_magic_completion_shadowing(self): # Before importing matplotlib, %matplotlib magic should be the only option. text, matches = c.complete("mat") - nt.assert_equal(matches, ["%matplotlib"]) + self.assertEqual(matches, ["%matplotlib"]) # The newly introduced name should shadow the magic. ip.run_cell("matplotlib = 1") text, matches = c.complete("mat") - nt.assert_equal(matches, ["matplotlib"]) + self.assertEqual(matches, ["matplotlib"]) # After removing matplotlib from namespace, the magic should again be # the only option. del ip.user_ns["matplotlib"] text, matches = c.complete("mat") - nt.assert_equal(matches, ["%matplotlib"]) + self.assertEqual(matches, ["%matplotlib"]) def test_magic_completion_shadowing_explicit(self): """ @@ -769,62 +766,62 @@ def test_magic_completion_shadowing_explicit(self): # Before importing matplotlib, %matplotlib magic should be the only option. text, matches = c.complete("%mat") - nt.assert_equal(matches, ["%matplotlib"]) + self.assertEqual(matches, ["%matplotlib"]) ip.run_cell("matplotlib = 1") # After removing matplotlib from namespace, the magic should still be # the only option. text, matches = c.complete("%mat") - nt.assert_equal(matches, ["%matplotlib"]) + self.assertEqual(matches, ["%matplotlib"]) def test_magic_config(self): ip = get_ipython() c = ip.Completer s, matches = c.complete(None, "conf") - nt.assert_in("%config", matches) + self.assertIn("%config", matches) s, matches = c.complete(None, "conf") - nt.assert_not_in("AliasManager", matches) + self.assertNotIn("AliasManager", matches) s, matches = c.complete(None, "config ") - nt.assert_in("AliasManager", matches) + self.assertIn("AliasManager", matches) s, matches = c.complete(None, "%config ") - nt.assert_in("AliasManager", matches) + self.assertIn("AliasManager", matches) s, matches = c.complete(None, "config Ali") - nt.assert_list_equal(["AliasManager"], matches) + self.assertListEqual(["AliasManager"], matches) s, matches = c.complete(None, "%config Ali") - nt.assert_list_equal(["AliasManager"], matches) + self.assertListEqual(["AliasManager"], matches) s, matches = c.complete(None, "config AliasManager") - nt.assert_list_equal(["AliasManager"], matches) + self.assertListEqual(["AliasManager"], matches) s, matches = c.complete(None, "%config AliasManager") - nt.assert_list_equal(["AliasManager"], matches) + self.assertListEqual(["AliasManager"], matches) s, matches = c.complete(None, "config AliasManager.") - nt.assert_in("AliasManager.default_aliases", matches) + self.assertIn("AliasManager.default_aliases", matches) s, matches = c.complete(None, "%config AliasManager.") - nt.assert_in("AliasManager.default_aliases", matches) + self.assertIn("AliasManager.default_aliases", matches) s, matches = c.complete(None, "config AliasManager.de") - nt.assert_list_equal(["AliasManager.default_aliases"], matches) + self.assertListEqual(["AliasManager.default_aliases"], matches) s, matches = c.complete(None, "config AliasManager.de") - nt.assert_list_equal(["AliasManager.default_aliases"], matches) + self.assertListEqual(["AliasManager.default_aliases"], matches) def test_magic_color(self): ip = get_ipython() c = ip.Completer s, matches = c.complete(None, "colo") - nt.assert_in("%colors", matches) + self.assertIn("%colors", matches) s, matches = c.complete(None, "colo") - nt.assert_not_in("NoColor", matches) + self.assertNotIn("NoColor", matches) s, matches = c.complete(None, "%colors") # No trailing space - nt.assert_not_in("NoColor", matches) + self.assertNotIn("NoColor", matches) s, matches = c.complete(None, "colors ") - nt.assert_in("NoColor", matches) + self.assertIn("NoColor", matches) s, matches = c.complete(None, "%colors ") - nt.assert_in("NoColor", matches) + self.assertIn("NoColor", matches) s, matches = c.complete(None, "colors NoCo") - nt.assert_list_equal(["NoColor"], matches) + self.assertListEqual(["NoColor"], matches) s, matches = c.complete(None, "%colors NoCo") - nt.assert_list_equal(["NoColor"], matches) + self.assertListEqual(["NoColor"], matches) def test_match_dict_keys(self): """ @@ -884,34 +881,34 @@ def test_dict_key_completion_string(self): # check completion at different stages _, matches = complete(line_buffer="d[") - nt.assert_in("'abc'", matches) - nt.assert_not_in("'abc']", matches) + self.assertIn("'abc'", matches) + self.assertNotIn("'abc']", matches) _, matches = complete(line_buffer="d['") - nt.assert_in("abc", matches) - nt.assert_not_in("abc']", matches) + self.assertIn("abc", matches) + self.assertNotIn("abc']", matches) _, matches = complete(line_buffer="d['a") - nt.assert_in("abc", matches) - nt.assert_not_in("abc']", matches) + self.assertIn("abc", matches) + self.assertNotIn("abc']", matches) # check use of different quoting _, matches = complete(line_buffer='d["') - nt.assert_in("abc", matches) - nt.assert_not_in('abc"]', matches) + self.assertIn("abc", matches) + self.assertNotIn('abc"]', matches) _, matches = complete(line_buffer='d["a') - nt.assert_in("abc", matches) - nt.assert_not_in('abc"]', matches) + self.assertIn("abc", matches) + self.assertNotIn('abc"]', matches) # check sensitivity to following context _, matches = complete(line_buffer="d[]", cursor_pos=2) - nt.assert_in("'abc'", matches) + self.assertIn("'abc'", matches) _, matches = complete(line_buffer="d['']", cursor_pos=3) - nt.assert_in("abc", matches) - nt.assert_not_in("abc'", matches) - nt.assert_not_in("abc']", matches) + self.assertIn("abc", matches) + self.assertNotIn("abc'", matches) + self.assertNotIn("abc']", matches) # check multiple solutions are correctly returned and that noise is not ip.user_ns["d"] = { @@ -925,111 +922,110 @@ def test_dict_key_completion_string(self): } _, matches = complete(line_buffer="d['a") - nt.assert_in("abc", matches) - nt.assert_in("abd", matches) - nt.assert_not_in("bad", matches) - nt.assert_not_in("abe", matches) - nt.assert_not_in("abf", matches) + self.assertIn("abc", matches) + self.assertIn("abd", matches) + self.assertNotIn("bad", matches) + self.assertNotIn("abe", matches) + self.assertNotIn("abf", matches) assert not any(m.endswith(("]", '"', "'")) for m in matches), matches # check escaping and whitespace ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None} _, matches = complete(line_buffer="d['a") - nt.assert_in("a\\nb", matches) - nt.assert_in("a\\'b", matches) - nt.assert_in('a"b', matches) - nt.assert_in("a word", matches) + self.assertIn("a\\nb", matches) + self.assertIn("a\\'b", matches) + self.assertIn('a"b', matches) + self.assertIn("a word", matches) assert not any(m.endswith(("]", '"', "'")) for m in matches), matches # - can complete on non-initial word of the string _, matches = complete(line_buffer="d['a w") - nt.assert_in("word", matches) + self.assertIn("word", matches) # - understands quote escaping _, matches = complete(line_buffer="d['a\\'") - nt.assert_in("b", matches) + self.assertIn("b", matches) # - default quoting should work like repr _, matches = complete(line_buffer="d[") - nt.assert_in('"a\'b"', matches) + self.assertIn('"a\'b"', matches) # - when opening quote with ", possible to match with unescaped apostrophe _, matches = complete(line_buffer="d[\"a'") - nt.assert_in("b", matches) + self.assertIn("b", matches) # need to not split at delims that readline won't split at if "-" not in ip.Completer.splitter.delims: ip.user_ns["d"] = {"before-after": None} _, matches = complete(line_buffer="d['before-af") - nt.assert_in("before-after", matches) + self.assertIn("before-after", matches) # check completion on tuple-of-string keys at different stage - on first key ip.user_ns["d"] = {('foo', 'bar'): None} _, matches = complete(line_buffer="d[") - nt.assert_in("'foo'", matches) - nt.assert_not_in("'foo']", matches) - nt.assert_not_in("'bar'", matches) - nt.assert_not_in("foo", matches) - nt.assert_not_in("bar", matches) + self.assertIn("'foo'", matches) + self.assertNotIn("'foo']", matches) + self.assertNotIn("'bar'", matches) + self.assertNotIn("foo", matches) + self.assertNotIn("bar", matches) # - match the prefix _, matches = complete(line_buffer="d['f") - nt.assert_in("foo", matches) - nt.assert_not_in("foo']", matches) - nt.assert_not_in("foo\"]", matches) + self.assertIn("foo", matches) + self.assertNotIn("foo']", matches) + self.assertNotIn('foo"]', matches) _, matches = complete(line_buffer="d['foo") - nt.assert_in("foo", matches) + self.assertIn("foo", matches) # - can complete on second key _, matches = complete(line_buffer="d['foo', ") - nt.assert_in("'bar'", matches) + self.assertIn("'bar'", matches) _, matches = complete(line_buffer="d['foo', 'b") - nt.assert_in("bar", matches) - nt.assert_not_in("foo", matches) + self.assertIn("bar", matches) + self.assertNotIn("foo", matches) # - does not propose missing keys _, matches = complete(line_buffer="d['foo', 'f") - nt.assert_not_in("bar", matches) - nt.assert_not_in("foo", matches) + self.assertNotIn("bar", matches) + self.assertNotIn("foo", matches) # check sensitivity to following context _, matches = complete(line_buffer="d['foo',]", cursor_pos=8) - nt.assert_in("'bar'", matches) - nt.assert_not_in("bar", matches) - nt.assert_not_in("'foo'", matches) - nt.assert_not_in("foo", matches) + self.assertIn("'bar'", matches) + self.assertNotIn("bar", matches) + self.assertNotIn("'foo'", matches) + self.assertNotIn("foo", matches) _, matches = complete(line_buffer="d['']", cursor_pos=3) - nt.assert_in("foo", matches) + self.assertIn("foo", matches) assert not any(m.endswith(("]", '"', "'")) for m in matches), matches _, matches = complete(line_buffer='d[""]', cursor_pos=3) - nt.assert_in("foo", matches) + self.assertIn("foo", matches) assert not any(m.endswith(("]", '"', "'")) for m in matches), matches _, matches = complete(line_buffer='d["foo","]', cursor_pos=9) - nt.assert_in("bar", matches) + self.assertIn("bar", matches) assert not any(m.endswith(("]", '"', "'")) for m in matches), matches _, matches = complete(line_buffer='d["foo",]', cursor_pos=8) - nt.assert_in("'bar'", matches) - nt.assert_not_in("bar", matches) + self.assertIn("'bar'", matches) + self.assertNotIn("bar", matches) # Can complete with longer tuple keys ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None} # - can complete second key _, matches = complete(line_buffer="d['foo', 'b") - nt.assert_in('bar', matches) - nt.assert_not_in('foo', matches) - nt.assert_not_in('foobar', matches) + self.assertIn("bar", matches) + self.assertNotIn("foo", matches) + self.assertNotIn("foobar", matches) # - can complete third key _, matches = complete(line_buffer="d['foo', 'bar', 'fo") - nt.assert_in('foobar', matches) - nt.assert_not_in('foo', matches) - nt.assert_not_in('bar', matches) - + self.assertIn("foobar", matches) + self.assertNotIn("foo", matches) + self.assertNotIn("bar", matches) def test_dict_key_completion_contexts(self): """Test expression contexts in which dict key completion occurs""" @@ -1046,16 +1042,16 @@ class C: def assert_no_completion(**kwargs): _, matches = complete(**kwargs) - nt.assert_not_in("abc", matches) - nt.assert_not_in("abc'", matches) - nt.assert_not_in("abc']", matches) - nt.assert_not_in("'abc'", matches) - nt.assert_not_in("'abc']", matches) + self.assertNotIn("abc", matches) + self.assertNotIn("abc'", matches) + self.assertNotIn("abc']", matches) + self.assertNotIn("'abc'", matches) + self.assertNotIn("'abc']", matches) def assert_completion(**kwargs): _, matches = complete(**kwargs) - nt.assert_in("'abc'", matches) - nt.assert_not_in("'abc']", matches) + self.assertIn("'abc'", matches) + self.assertNotIn("'abc']", matches) # no completion after string closed, even if reopened assert_no_completion(line_buffer="d['a'") @@ -1071,7 +1067,7 @@ def assert_completion(**kwargs): # greedy flag def assert_completion(**kwargs): _, matches = complete(**kwargs) - nt.assert_in("get()['abc']", matches) + self.assertIn("get()['abc']", matches) assert_no_completion(line_buffer="get()[") with greedy_completion(): @@ -1089,25 +1085,25 @@ def test_dict_key_completion_bytes(self): ip.user_ns["d"] = {"abc": None, b"abd": None} _, matches = complete(line_buffer="d[") - nt.assert_in("'abc'", matches) - nt.assert_in("b'abd'", matches) + self.assertIn("'abc'", matches) + self.assertIn("b'abd'", matches) if False: # not currently implemented _, matches = complete(line_buffer="d[b") - nt.assert_in("b'abd'", matches) - nt.assert_not_in("b'abc'", matches) + self.assertIn("b'abd'", matches) + self.assertNotIn("b'abc'", matches) _, matches = complete(line_buffer="d[b'") - nt.assert_in("abd", matches) - nt.assert_not_in("abc", matches) + self.assertIn("abd", matches) + self.assertNotIn("abc", matches) _, matches = complete(line_buffer="d[B'") - nt.assert_in("abd", matches) - nt.assert_not_in("abc", matches) + self.assertIn("abd", matches) + self.assertNotIn("abc", matches) _, matches = complete(line_buffer="d['") - nt.assert_in("abc", matches) - nt.assert_not_in("abd", matches) + self.assertIn("abc", matches) + self.assertNotIn("abd", matches) def test_dict_key_completion_unicode_py3(self): """Test handling of unicode in dict key completion""" @@ -1120,20 +1116,20 @@ def test_dict_key_completion_unicode_py3(self): if sys.platform != "win32": # Known failure on Windows _, matches = complete(line_buffer="d['a\\u05d0") - nt.assert_in("u05d0", matches) # tokenized after \\ + self.assertIn("u05d0", matches) # tokenized after \\ # query using character _, matches = complete(line_buffer="d['a\u05d0") - nt.assert_in("a\u05d0", matches) + self.assertIn("a\u05d0", matches) with greedy_completion(): # query using escape _, matches = complete(line_buffer="d['a\\u05d0") - nt.assert_in("d['a\\u05d0']", matches) # tokenized after \\ + self.assertIn("d['a\\u05d0']", matches) # tokenized after \\ # query using character _, matches = complete(line_buffer="d['a\u05d0") - nt.assert_in("d['a\u05d0']", matches) + self.assertIn("d['a\u05d0']", matches) @dec.skip_without("numpy") def test_struct_array_key_completion(self): @@ -1144,8 +1140,8 @@ def test_struct_array_key_completion(self): complete = ip.Completer.complete ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")]) _, matches = complete(line_buffer="d['") - nt.assert_in("hello", matches) - nt.assert_in("world", matches) + self.assertIn("hello", matches) + self.assertIn("world", matches) # complete on the numpy struct itself dt = numpy.dtype( [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)] @@ -1153,14 +1149,14 @@ def test_struct_array_key_completion(self): x = numpy.zeros(2, dtype=dt) ip.user_ns["d"] = x[1] _, matches = complete(line_buffer="d['") - nt.assert_in("my_head", matches) - nt.assert_in("my_data", matches) + self.assertIn("my_head", matches) + self.assertIn("my_data", matches) # complete on a nested level with greedy_completion(): ip.user_ns["d"] = numpy.zeros(2, dtype=dt) _, matches = complete(line_buffer="d[1]['my_head']['") - nt.assert_true(any(["my_dt" in m for m in matches])) - nt.assert_true(any(["my_df" in m for m in matches])) + self.assertTrue(any(["my_dt" in m for m in matches])) + self.assertTrue(any(["my_df" in m for m in matches])) @dec.skip_without("pandas") def test_dataframe_key_completion(self): @@ -1171,8 +1167,8 @@ def test_dataframe_key_completion(self): complete = ip.Completer.complete ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]}) _, matches = complete(line_buffer="d['") - nt.assert_in("hello", matches) - nt.assert_in("world", matches) + self.assertIn("hello", matches) + self.assertIn("world", matches) def test_dict_key_completion_invalids(self): """Smoke test cases dict key completion can't handle""" @@ -1197,8 +1193,8 @@ def test_object_key_completion(self): ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"]) _, matches = ip.Completer.complete(line_buffer="key_completable['qw") - nt.assert_in("qwerty", matches) - nt.assert_in("qwick", matches) + self.assertIn("qwerty", matches) + self.assertIn("qwick", matches) def test_class_key_completion(self): ip = get_ipython() @@ -1207,8 +1203,8 @@ def test_class_key_completion(self): ip.user_ns["named_instance_class"] = NamedInstanceClass _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw") - nt.assert_in("qwerty", matches) - nt.assert_in("qwick", matches) + self.assertIn("qwerty", matches) + self.assertIn("qwick", matches) def test_tryimport(self): """ @@ -1221,27 +1217,27 @@ def test_tryimport(self): def test_aimport_module_completer(self): ip = get_ipython() _, matches = ip.complete("i", "%aimport i") - nt.assert_in("io", matches) - nt.assert_not_in("int", matches) + self.assertIn("io", matches) + self.assertNotIn("int", matches) def test_nested_import_module_completer(self): ip = get_ipython() _, matches = ip.complete(None, "import IPython.co", 17) - nt.assert_in("IPython.core", matches) - nt.assert_not_in("import IPython.core", matches) - nt.assert_not_in("IPython.display", matches) + self.assertIn("IPython.core", matches) + self.assertNotIn("import IPython.core", matches) + self.assertNotIn("IPython.display", matches) def test_import_module_completer(self): ip = get_ipython() _, matches = ip.complete("i", "import i") - nt.assert_in("io", matches) - nt.assert_not_in("int", matches) + self.assertIn("io", matches) + self.assertNotIn("int", matches) def test_from_module_completer(self): ip = get_ipython() _, matches = ip.complete("B", "from io import B", 16) - nt.assert_in("BytesIO", matches) - nt.assert_not_in("BaseException", matches) + self.assertIn("BytesIO", matches) + self.assertNotIn("BaseException", matches) def test_snake_case_completion(self): ip = get_ipython() @@ -1249,8 +1245,8 @@ def test_snake_case_completion(self): ip.user_ns["some_three"] = 3 ip.user_ns["some_four"] = 4 _, matches = ip.complete("s_", "print(s_f") - nt.assert_in("some_three", matches) - nt.assert_in("some_four", matches) + self.assertIn("some_three", matches) + self.assertIn("some_four", matches) def test_mix_terms(self): ip = get_ipython() @@ -1274,5 +1270,5 @@ def meth_2(self, meth2_arg1, meth2_arg2): ) ) _, matches = ip.complete(None, "test.meth(") - nt.assert_in("meth_arg1=", matches) - nt.assert_not_in("meth2_arg1=", matches) + self.assertIn("meth_arg1=", matches) + self.assertNotIn("meth2_arg1=", matches) From 44d74a9af58956162aef50022942b876f3423d6d Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:56:44 +0200 Subject: [PATCH 1585/3726] [core][tests][completerlib] Remove nose --- IPython/core/tests/test_completerlib.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_completerlib.py b/IPython/core/tests/test_completerlib.py index f87e784d63c..b5508447895 100644 --- a/IPython/core/tests/test_completerlib.py +++ b/IPython/core/tests/test_completerlib.py @@ -14,8 +14,6 @@ import unittest from os.path import join -import nose.tools as nt - from IPython.core.completerlib import magic_run_completer, module_completion, try_import from IPython.utils.tempdir import TemporaryDirectory from IPython.testing.decorators import onlyif_unicode_paths @@ -140,7 +138,7 @@ def test_import_invalid_module(): s = set( module_completion('import foo') ) intersection = s.intersection(invalid_module_names) - nt.assert_equal(intersection, set()) + assert intersection == set() assert valid_module_names.issubset(s), valid_module_names.intersection(s) @@ -153,15 +151,15 @@ def test_bad_module_all(): testsdir = os.path.dirname(__file__) sys.path.insert(0, testsdir) try: - results = module_completion('from bad_all import ') - nt.assert_in('puppies', results) + results = module_completion("from bad_all import ") + assert "puppies" in results for r in results: - nt.assert_is_instance(r, str) + assert isinstance(r, str) # bad_all doesn't contain submodules, but this completion # should finish without raising an exception: results = module_completion("import bad_all.") - nt.assert_equal(results, []) + assert results == [] finally: sys.path.remove(testsdir) @@ -189,6 +187,6 @@ def test_valid_exported_submodules(): """ results = module_completion("import os.pa") # ensure we get a valid submodule: - nt.assert_in("os.path", results) + assert "os.path" in results # ensure we don't get objects that aren't submodules: - nt.assert_not_in("os.pathconf", results) + assert "os.pathconf" not in results From 7880f76fed6c1c06f07f4e1afa2ca6aa4b7a5092 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 10:57:02 +0200 Subject: [PATCH 1586/3726] [core][tests][debugger] Remove nose --- IPython/core/tests/test_debugger.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 7e1a07d5fb1..ab6b3cb0233 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -18,8 +18,6 @@ from textwrap import dedent from unittest.mock import patch -import nose.tools as nt - from IPython.core import debugger from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE from IPython.testing.decorators import skip_win32 @@ -66,13 +64,13 @@ def test_longer_repr(): a = '1234567890'* 7 ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'" a_trunc = "'123456789012...8901234567890'" - nt.assert_equal(trepr(a), a_trunc) + assert trepr(a) == a_trunc # The creation of our tracer modifies the repr module's repr function # in-place, since that global is used directly by the stdlib's pdb module. with warnings.catch_warnings(): warnings.simplefilter('ignore', DeprecationWarning) debugger.Tracer() - nt.assert_equal(trepr(a), ar) + assert trepr(a) == ar def test_ipdb_magics(): '''Test calling some IPython magics from ipdb. From f4d8de49fa6aeadc4f0bceceff440b04a6136fb3 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:10:59 +0200 Subject: [PATCH 1587/3726] [core][tests][display] Remove nose --- IPython/core/tests/test_display.py | 245 ++++++++++++++++------------- 1 file changed, 133 insertions(+), 112 deletions(-) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index 15cae218587..b88bee8c8c1 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -7,7 +7,7 @@ from unittest import mock -import nose.tools as nt +import pytest from IPython import display from IPython.core.getipython import get_ipython @@ -22,15 +22,15 @@ def test_image_size(): """Simple test for display.Image(args, width=x,height=y)""" thisurl = 'http://www.google.fr/images/srpr/logo3w.png' img = display.Image(url=thisurl, width=200, height=200) - nt.assert_equal(u'' % (thisurl), img._repr_html_()) + assert '' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl, metadata={'width':200, 'height':200}) - nt.assert_equal(u'' % (thisurl), img._repr_html_()) + assert '' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl, width=200) - nt.assert_equal(u'' % (thisurl), img._repr_html_()) + assert '' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl) - nt.assert_equal(u'' % (thisurl), img._repr_html_()) + assert '' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl, unconfined=True) - nt.assert_equal(u'' % (thisurl), img._repr_html_()) + assert '' % (thisurl) == img._repr_html_() def test_image_mimes(): @@ -39,7 +39,7 @@ def test_image_mimes(): mime = display.Image._MIMETYPES[format] img = display.Image(b'garbage', format=format) data, metadata = fmt(img) - nt.assert_equal(sorted(data), sorted([mime, 'text/plain'])) + assert sorted(data) == sorted([mime, "text/plain"]) def test_geojson(): @@ -60,17 +60,20 @@ def test_geojson(): "attribution": "Celestia/praesepe", "minZoom": 0, "maxZoom": 18, - }) - nt.assert_equal(u'', str(gj)) + }, + ) + assert "" == str(gj) + def test_retina_png(): here = os.path.dirname(__file__) img = display.Image(os.path.join(here, "2x2.png"), retina=True) - nt.assert_equal(img.height, 1) - nt.assert_equal(img.width, 1) + assert img.height == 1 + assert img.width == 1 data, md = img._repr_png_() - nt.assert_equal(md['width'], 1) - nt.assert_equal(md['height'], 1) + assert md["width"] == 1 + assert md["height"] == 1 + def test_embed_svg_url(): import gzip @@ -102,18 +105,20 @@ def read(self): with mock.patch('urllib.request.urlopen', side_effect=mocked_urlopen): svg = display.SVG(url=url) - nt.assert_true(svg._repr_svg_().startswith('') - nt.assert_equal(p.data, 'This is a simple test') + p = display.Pretty("This is a simple test") + assert repr(p) == "" + assert p.data == "This is a simple test" + + p._show_mem_addr = True + assert repr(p) == object.__repr__(p) - p._show_mem_addr = True - nt.assert_equal(repr(p), object.__repr__(p)) def test_displayobject_repr(): - h = display.HTML('
') - nt.assert_equal(repr(h), '') + h = display.HTML("
") + assert repr(h) == "" h._show_mem_addr = True - nt.assert_equal(repr(h), object.__repr__(h)) + assert repr(h) == object.__repr__(h) h._show_mem_addr = False - nt.assert_equal(repr(h), '') + assert repr(h) == "" - j = display.Javascript('') - nt.assert_equal(repr(j), '') + j = display.Javascript("") + assert repr(j) == "" j._show_mem_addr = True - nt.assert_equal(repr(j), object.__repr__(j)) + assert repr(j) == object.__repr__(j) j._show_mem_addr = False - nt.assert_equal(repr(j), '') + assert repr(j) == "" @mock.patch('warnings.warn') def test_encourage_iframe_over_html(m_warn): @@ -255,18 +273,22 @@ def test_encourage_iframe_over_html(m_warn): def test_progress(): p = display.ProgressBar(10) - nt.assert_in('0/10',repr(p)) - p.html_width = '100%' + assert "0/10" in repr(p) + p.html_width = "100%" p.progress = 5 - nt.assert_equal(p._repr_html_(), "") + assert ( + p._repr_html_() == "" + ) + def test_progress_iter(): with capture_output(display=False) as captured: for i in display.ProgressBar(5): out = captured.stdout - nt.assert_in('{0}/5'.format(i), out) + assert "{0}/5".format(i) in out out = captured.stdout - nt.assert_in('5/5', out) + assert "5/5" in out + def test_json(): d = {'a': 5} @@ -284,13 +306,13 @@ def test_json(): display.JSON(d, expanded=True, root='custom'), ] for j, md in zip(json_objs, metadata): - nt.assert_equal(j._repr_json_(), (d, md)) + assert j._repr_json_() == (d, md) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") j = display.JSON(json.dumps(d)) - nt.assert_equal(len(w), 1) - nt.assert_equal(j._repr_json_(), (d, metadata[0])) + assert len(w) == 1 + assert j._repr_json_() == (d, metadata[0]) json_objs = [ display.JSON(lis), @@ -299,23 +321,24 @@ def test_json(): display.JSON(lis, expanded=True, root='custom'), ] for j, md in zip(json_objs, metadata): - nt.assert_equal(j._repr_json_(), (lis, md)) + assert j._repr_json_() == (lis, md) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") j = display.JSON(json.dumps(lis)) - nt.assert_equal(len(w), 1) - nt.assert_equal(j._repr_json_(), (lis, metadata[0])) + assert len(w) == 1 + assert j._repr_json_() == (lis, metadata[0]) + def test_video_embedding(): """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash""" v = display.Video("http://ignored") assert not v.embed html = v._repr_html_() - nt.assert_not_in('src="data:', html) - nt.assert_in('src="http://ignored"', html) + assert 'src="data:' not in html + assert 'src="http://ignored"' in html - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): v = display.Video(b'abc') with NamedFileInTemporaryDirectory('test.mp4') as f: @@ -325,52 +348,53 @@ def test_video_embedding(): v = display.Video(f.name) assert not v.embed html = v._repr_html_() - nt.assert_not_in('src="data:', html) + assert 'src="data:' not in html v = display.Video(f.name, embed=True) html = v._repr_html_() - nt.assert_in('src="data:video/mp4;base64,YWJj"',html) + assert 'src="data:video/mp4;base64,YWJj"' in html v = display.Video(f.name, embed=True, mimetype='video/other') html = v._repr_html_() - nt.assert_in('src="data:video/other;base64,YWJj"',html) + assert 'src="data:video/other;base64,YWJj"' in html v = display.Video(b'abc', embed=True, mimetype='video/mp4') html = v._repr_html_() - nt.assert_in('src="data:video/mp4;base64,YWJj"',html) + assert 'src="data:video/mp4;base64,YWJj"' in html v = display.Video(u'YWJj', embed=True, mimetype='video/xyz') html = v._repr_html_() - nt.assert_in('src="data:video/xyz;base64,YWJj"',html) + assert 'src="data:video/xyz;base64,YWJj"' in html def test_html_metadata(): s = "

Test

" h = display.HTML(s, metadata={"isolated": True}) - nt.assert_equal(h._repr_html_(), (s, {"isolated": True})) + assert h._repr_html_() == (s, {"isolated": True}) + def test_display_id(): ip = get_ipython() with mock.patch.object(ip.display_pub, 'publish') as pub: handle = display.display('x') - nt.assert_is(handle, None) + assert handle is None handle = display.display('y', display_id='secret') - nt.assert_is_instance(handle, display.DisplayHandle) + assert isinstance(handle, display.DisplayHandle) handle2 = display.display('z', display_id=True) - nt.assert_is_instance(handle2, display.DisplayHandle) - nt.assert_not_equal(handle.display_id, handle2.display_id) + assert isinstance(handle2, display.DisplayHandle) + assert handle.display_id != handle2.display_id - nt.assert_equal(pub.call_count, 3) + assert pub.call_count == 3 args, kwargs = pub.call_args_list[0] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('x') }, 'metadata': {}, - }) + } args, kwargs = pub.call_args_list[1] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('y') }, @@ -378,10 +402,10 @@ def test_display_id(): 'transient': { 'display_id': handle.display_id, }, - }) + } args, kwargs = pub.call_args_list[2] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('z') }, @@ -389,19 +413,19 @@ def test_display_id(): 'transient': { 'display_id': handle2.display_id, }, - }) + } def test_update_display(): ip = get_ipython() with mock.patch.object(ip.display_pub, 'publish') as pub: - with nt.assert_raises(TypeError): + with pytest.raises(TypeError): display.update_display('x') display.update_display('x', display_id='1') display.update_display('y', display_id='2') args, kwargs = pub.call_args_list[0] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('x') }, @@ -410,10 +434,10 @@ def test_update_display(): 'display_id': '1', }, 'update': True, - }) + } args, kwargs = pub.call_args_list[1] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('y') }, @@ -422,22 +446,22 @@ def test_update_display(): 'display_id': '2', }, 'update': True, - }) + } def test_display_handle(): ip = get_ipython() handle = display.DisplayHandle() - nt.assert_is_instance(handle.display_id, str) - handle = display.DisplayHandle('my-id') - nt.assert_equal(handle.display_id, 'my-id') - with mock.patch.object(ip.display_pub, 'publish') as pub: - handle.display('x') - handle.update('y') + assert isinstance(handle.display_id, str) + handle = display.DisplayHandle("my-id") + assert handle.display_id == "my-id" + with mock.patch.object(ip.display_pub, "publish") as pub: + handle.display("x") + handle.update("y") args, kwargs = pub.call_args_list[0] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('x') }, @@ -445,10 +469,10 @@ def test_display_handle(): 'transient': { 'display_id': handle.display_id, } - }) + } args, kwargs = pub.call_args_list[1] - nt.assert_equal(args, ()) - nt.assert_equal(kwargs, { + assert args == () + assert kwargs == { 'data': { 'text/plain': repr('y') }, @@ -457,34 +481,31 @@ def test_display_handle(): 'display_id': handle.display_id, }, 'update': True, - }) + } def test_image_alt_tag(): """Simple test for display.Image(args, alt=x,)""" thisurl = "http://example.com/image.png" img = display.Image(url=thisurl, alt="an image") - nt.assert_equal(u'an image' % (thisurl), img._repr_html_()) + assert 'an image' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl, unconfined=True, alt="an image") - nt.assert_equal( - u'an image' % (thisurl), - img._repr_html_(), + assert ( + 'an image' % (thisurl) + == img._repr_html_() ) img = display.Image(url=thisurl, alt='>"& <') - nt.assert_equal( - u'>"& <' % (thisurl), img._repr_html_() - ) + assert '>"& <' % (thisurl) == img._repr_html_() img = display.Image(url=thisurl, metadata={"alt": "an image"}) - nt.assert_equal(img.alt, "an image") - + assert img.alt == "an image" here = os.path.dirname(__file__) img = display.Image(os.path.join(here, "2x2.png"), alt="an image") - nt.assert_equal(img.alt, "an image") + assert img.alt == "an image" _, md = img._repr_png_() - nt.assert_equal(md["alt"], "an image") + assert md["alt"] == "an image" -@nt.raises(FileNotFoundError) def test_image_bad_filename_raises_proper_exception(): - display.Image("/this/file/does/not/exist/")._repr_png_() + with pytest.raises(FileNotFoundError): + display.Image("/this/file/does/not/exist/")._repr_png_() From 8f530016199fb5e32955f4d4a78e34ef861095c1 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:11:46 +0200 Subject: [PATCH 1588/3726] [core][tests][events] Remove nose --- IPython/core/tests/test_events.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/IPython/core/tests/test_events.py b/IPython/core/tests/test_events.py index a4211ecea4c..cc9bf40fd6d 100644 --- a/IPython/core/tests/test_events.py +++ b/IPython/core/tests/test_events.py @@ -1,6 +1,5 @@ import unittest from unittest.mock import Mock -import nose.tools as nt from IPython.core import events import IPython.testing.tools as tt @@ -40,9 +39,9 @@ def cb1(): def cb2(): ... - self.em.register('ping_received', cb1) - nt.assert_raises(ValueError, self.em.unregister, 'ping_received', cb2) - self.em.unregister('ping_received', cb1) + self.em.register("ping_received", cb1) + self.assertRaises(ValueError, self.em.unregister, "ping_received", cb2) + self.em.unregister("ping_received", cb1) def test_cb_error(self): cb = Mock(side_effect=ValueError) From 1304d9e8b37bc2df27974a7679dd17edc8bf9e38 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:12:00 +0200 Subject: [PATCH 1589/3726] [core][tests][extension] Remove nose --- IPython/core/tests/test_extension.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/IPython/core/tests/test_extension.py b/IPython/core/tests/test_extension.py index b18e6848fe1..51db4b6258c 100644 --- a/IPython/core/tests/test_extension.py +++ b/IPython/core/tests/test_extension.py @@ -1,7 +1,5 @@ import os.path -import nose.tools as nt - import IPython.testing.tools as tt from IPython.utils.syspathcontext import prepended_to_syspath from IPython.utils.tempdir import TemporaryDirectory @@ -93,4 +91,4 @@ def test_extension_builtins(): def test_non_extension(): em = get_ipython().extension_manager - nt.assert_equal(em.load_extension('sys'), "no load function") + assert em.load_extension("sys") == "no load function" From 2de4d8b31bd091ea60c6b5301ebdabcfe855aa04 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:13:46 +0200 Subject: [PATCH 1590/3726] [core][tests][formatters] Remove nose --- IPython/core/tests/test_formatters.py | 164 +++++++++++++------------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 15ce8df29d1..6ea40eb22b6 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -7,7 +7,7 @@ import numpy except: numpy = None -import nose.tools as nt +import pytest from IPython import get_ipython from traitlets.config import Config @@ -95,24 +95,25 @@ def test_bad_precision(): """test various invalid values for float_precision.""" f = PlainTextFormatter() def set_fp(p): - f.float_precision=p - nt.assert_raises(ValueError, set_fp, '%') - nt.assert_raises(ValueError, set_fp, '%.3f%i') - nt.assert_raises(ValueError, set_fp, 'foo') - nt.assert_raises(ValueError, set_fp, -1) + f.float_precision = p + + pytest.raises(ValueError, set_fp, "%") + pytest.raises(ValueError, set_fp, "%.3f%i") + pytest.raises(ValueError, set_fp, "foo") + pytest.raises(ValueError, set_fp, -1) def test_for_type(): f = PlainTextFormatter() # initial return, None - nt.assert_is(f.for_type(C, foo_printer), None) + assert f.for_type(C, foo_printer) is None # no func queries - nt.assert_is(f.for_type(C), foo_printer) + assert f.for_type(C) is foo_printer # shouldn't change anything - nt.assert_is(f.for_type(C), foo_printer) + assert f.for_type(C) is foo_printer # None should do the same - nt.assert_is(f.for_type(C, None), foo_printer) - nt.assert_is(f.for_type(C, None), foo_printer) + assert f.for_type(C, None) is foo_printer + assert f.for_type(C, None) is foo_printer def test_for_type_string(): f = PlainTextFormatter() @@ -120,13 +121,13 @@ def test_for_type_string(): type_str = '%s.%s' % (C.__module__, 'C') # initial return, None - nt.assert_is(f.for_type(type_str, foo_printer), None) + assert f.for_type(type_str, foo_printer) is None # no func queries - nt.assert_is(f.for_type(type_str), foo_printer) - nt.assert_in(_mod_name_key(C), f.deferred_printers) - nt.assert_is(f.for_type(C), foo_printer) - nt.assert_not_in(_mod_name_key(C), f.deferred_printers) - nt.assert_in(C, f.type_printers) + assert f.for_type(type_str) is foo_printer + assert _mod_name_key(C) in f.deferred_printers + assert f.for_type(C) is foo_printer + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers def test_for_type_by_name(): f = PlainTextFormatter() @@ -134,21 +135,22 @@ def test_for_type_by_name(): mod = C.__module__ # initial return, None - nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None) + assert f.for_type_by_name(mod, "C", foo_printer) is None # no func queries - nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer) + assert f.for_type_by_name(mod, "C") is foo_printer # shouldn't change anything - nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer) + assert f.for_type_by_name(mod, "C") is foo_printer # None should do the same - nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer) - nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer) + assert f.for_type_by_name(mod, "C", None) is foo_printer + assert f.for_type_by_name(mod, "C", None) is foo_printer + def test_lookup(): f = PlainTextFormatter() f.for_type(C, foo_printer) - nt.assert_is(f.lookup(C()), foo_printer) - with nt.assert_raises(KeyError): + assert f.lookup(C()) is foo_printer + with pytest.raises(KeyError): f.lookup(A()) def test_lookup_string(): @@ -156,16 +158,16 @@ def test_lookup_string(): type_str = '%s.%s' % (C.__module__, 'C') f.for_type(type_str, foo_printer) - nt.assert_is(f.lookup(C()), foo_printer) + assert f.lookup(C()) is foo_printer # should move from deferred to imported dict - nt.assert_not_in(_mod_name_key(C), f.deferred_printers) - nt.assert_in(C, f.type_printers) + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers def test_lookup_by_type(): f = PlainTextFormatter() f.for_type(C, foo_printer) - nt.assert_is(f.lookup_by_type(C), foo_printer) - with nt.assert_raises(KeyError): + assert f.lookup_by_type(C) is foo_printer + with pytest.raises(KeyError): f.lookup_by_type(A) def test_lookup_by_type_string(): @@ -174,69 +176,69 @@ def test_lookup_by_type_string(): f.for_type(type_str, foo_printer) # verify insertion - nt.assert_in(_mod_name_key(C), f.deferred_printers) - nt.assert_not_in(C, f.type_printers) + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers - nt.assert_is(f.lookup_by_type(type_str), foo_printer) + assert f.lookup_by_type(type_str) is foo_printer # lookup by string doesn't cause import - nt.assert_in(_mod_name_key(C), f.deferred_printers) - nt.assert_not_in(C, f.type_printers) + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers - nt.assert_is(f.lookup_by_type(C), foo_printer) + assert f.lookup_by_type(C) is foo_printer # should move from deferred to imported dict - nt.assert_not_in(_mod_name_key(C), f.deferred_printers) - nt.assert_in(C, f.type_printers) + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers def test_in_formatter(): f = PlainTextFormatter() f.for_type(C, foo_printer) type_str = '%s.%s' % (C.__module__, 'C') - nt.assert_in(C, f) - nt.assert_in(type_str, f) + assert C in f + assert type_str in f def test_string_in_formatter(): f = PlainTextFormatter() type_str = '%s.%s' % (C.__module__, 'C') f.for_type(type_str, foo_printer) - nt.assert_in(type_str, f) - nt.assert_in(C, f) + assert type_str in f + assert C in f def test_pop(): f = PlainTextFormatter() f.for_type(C, foo_printer) - nt.assert_is(f.lookup_by_type(C), foo_printer) - nt.assert_is(f.pop(C, None), foo_printer) + assert f.lookup_by_type(C) is foo_printer + assert f.pop(C, None) is foo_printer f.for_type(C, foo_printer) - nt.assert_is(f.pop(C), foo_printer) - with nt.assert_raises(KeyError): + assert f.pop(C) is foo_printer + with pytest.raises(KeyError): f.lookup_by_type(C) - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.pop(C) - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.pop(A) - nt.assert_is(f.pop(A, None), None) + assert f.pop(A, None) is None def test_pop_string(): f = PlainTextFormatter() type_str = '%s.%s' % (C.__module__, 'C') - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.pop(type_str) f.for_type(type_str, foo_printer) f.pop(type_str) - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.lookup_by_type(C) - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.pop(type_str) f.for_type(C, foo_printer) - nt.assert_is(f.pop(type_str, None), foo_printer) - with nt.assert_raises(KeyError): + assert f.pop(type_str, None) is foo_printer + with pytest.raises(KeyError): f.lookup_by_type(C) - with nt.assert_raises(KeyError): + with pytest.raises(KeyError): f.pop(type_str) - nt.assert_is(f.pop(type_str, None), None) + assert f.pop(type_str, None) is None def test_error_method(): @@ -247,10 +249,10 @@ def _repr_html_(self): bad = BadHTML() with capture_output() as captured: result = f(bad) - nt.assert_is(result, None) - nt.assert_in("Traceback", captured.stdout) - nt.assert_in("Bad HTML", captured.stdout) - nt.assert_in("_repr_html_", captured.stdout) + assert result is None + assert "Traceback" in captured.stdout + assert "Bad HTML" in captured.stdout + assert "_repr_html_" in captured.stdout def test_nowarn_notimplemented(): f = HTMLFormatter() @@ -260,7 +262,7 @@ def _repr_html_(self): h = HTMLNotImplemented() with capture_output() as captured: result = f(h) - nt.assert_is(result, None) + assert result is None assert "" == captured.stderr assert "" == captured.stdout @@ -270,10 +272,10 @@ def test_warn_error_for_type(): f.for_type(int, lambda i: name_error) with capture_output() as captured: result = f(5) - nt.assert_is(result, None) - nt.assert_in("Traceback", captured.stdout) - nt.assert_in("NameError", captured.stdout) - nt.assert_in("name_error", captured.stdout) + assert result is None + assert "Traceback" in captured.stdout + assert "NameError" in captured.stdout + assert "name_error" in captured.stdout def test_error_pretty_method(): f = PlainTextFormatter() @@ -283,11 +285,11 @@ def _repr_pretty_(self): bad = BadPretty() with capture_output() as captured: result = f(bad) - nt.assert_is(result, None) - nt.assert_in("Traceback", captured.stdout) - nt.assert_in("_repr_pretty_", captured.stdout) - nt.assert_in("given", captured.stdout) - nt.assert_in("argument", captured.stdout) + assert result is None + assert "Traceback" in captured.stdout + assert "_repr_pretty_" in captured.stdout + assert "given" in captured.stdout + assert "argument" in captured.stdout def test_bad_repr_traceback(): @@ -296,10 +298,10 @@ def test_bad_repr_traceback(): with capture_output() as captured: result = f(bad) # catches error, returns None - nt.assert_is(result, None) - nt.assert_in("Traceback", captured.stdout) - nt.assert_in("__repr__", captured.stdout) - nt.assert_in("ValueError", captured.stdout) + assert result is None + assert "Traceback" in captured.stdout + assert "__repr__" in captured.stdout + assert "ValueError" in captured.stdout class MakePDF(object): @@ -319,8 +321,8 @@ def _repr_html_(self): return "hello" with capture_output() as captured: result = f(MyHTML) - nt.assert_is(result, None) - nt.assert_not_in("FormatterWarning", captured.stderr) + assert result is None + assert "FormatterWarning" not in captured.stderr with capture_output() as captured: result = f(MyHTML()) @@ -341,8 +343,8 @@ def __getattr__(self, key): with capture_output() as captured: result = f(text_hat) - nt.assert_is(result, None) - nt.assert_not_in("FormatterWarning", captured.stderr) + assert result is None + assert "FormatterWarning" not in captured.stderr class CallableMagicHat(object): def __getattr__(self, key): @@ -362,8 +364,8 @@ def _repr_html_(self, extra, args): with capture_output() as captured: result = f(bad) - nt.assert_is(result, None) - nt.assert_not_in("FormatterWarning", captured.stderr) + assert result is None + assert "FormatterWarning" not in captured.stderr def test_format_config(): @@ -372,12 +374,12 @@ def test_format_config(): cfg = Config() with capture_output() as captured: result = f(cfg) - nt.assert_is(result, None) + assert result is None assert captured.stderr == "" with capture_output() as captured: result = f(Config) - nt.assert_is(result, None) + assert result is None assert captured.stderr == "" From 2965bcf0e93dc31fc0b6b9c5d2732d1f9f1ea715 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:13:57 +0200 Subject: [PATCH 1591/3726] [core][tests][handlers] Remove nose --- IPython/core/tests/test_handlers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/IPython/core/tests/test_handlers.py b/IPython/core/tests/test_handlers.py index 19248177295..e151e70ee91 100644 --- a/IPython/core/tests/test_handlers.py +++ b/IPython/core/tests/test_handlers.py @@ -4,9 +4,6 @@ # Module imports #----------------------------------------------------------------------------- -# third party -import nose.tools as nt - # our own packages from IPython.core import autocall from IPython.testing import tools as tt @@ -91,4 +88,4 @@ def test_handlers(): ]) ip.magic('autocall 1') - nt.assert_equal(failures, []) + assert failures == [] From cf762a5685effaeed94a03a45927b87be5085175 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:19:49 +0200 Subject: [PATCH 1592/3726] [core][tests][history] Remove nose --- IPython/core/tests/test_history.py | 103 ++++++++++++++--------------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index 824ba1eeee0..4cc43caeb6b 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -13,9 +13,6 @@ from datetime import datetime import sqlite3 -# third party -import nose.tools as nt - # our own packages from traitlets.config.loader import Config from IPython.utils.tempdir import TemporaryDirectory @@ -23,7 +20,7 @@ from IPython.testing.decorators import skipif def test_proper_default_encoding(): - nt.assert_equal(sys.getdefaultencoding(), "utf-8") + assert sys.getdefaultencoding() == "utf-8" @skipif(sqlite3.sqlite_version_info > (3,24,0)) def test_history(): @@ -34,7 +31,7 @@ def test_history(): hist_file = tmp_path / "history.sqlite" try: ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file) - hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"] + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] for i, h in enumerate(hist, start=1): ip.history_manager.store_inputs(i, h) @@ -43,13 +40,15 @@ def test_history(): ip.history_manager.output_hist_reprs[3] = "spam" ip.history_manager.store_output(3) - nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) + assert ip.history_manager.input_hist_raw == [""] + hist # Detailed tests for _get_range_session grs = ip.history_manager._get_range_session - nt.assert_equal(list(grs(start=2,stop=-1)), list(zip([0], [2], hist[1:-1]))) - nt.assert_equal(list(grs(start=-2)), list(zip([0,0], [2,3], hist[-2:]))) - nt.assert_equal(list(grs(output=True)), list(zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))) + assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1])) + assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:])) + assert list(grs(output=True)) == list( + zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"])) + ) # Check whether specifying a range beyond the end of the current # session results in an error (gh-804) @@ -63,17 +62,14 @@ def test_history(): # New session ip.history_manager.reset() - newcmds = [u"z=5", - u"class X(object):\n pass", - u"k='p'", - u"z=5"] + newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"] for i, cmd in enumerate(newcmds, start=1): ip.history_manager.store_inputs(i, cmd) gothist = ip.history_manager.get_range(start=1, stop=4) - nt.assert_equal(list(gothist), list(zip([0,0,0],[1,2,3], newcmds))) + assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds)) # Previous session: gothist = ip.history_manager.get_range(-1, 1, 4) - nt.assert_equal(list(gothist), list(zip([1,1,1],[1,2,3], hist))) + assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist)) newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)] @@ -82,62 +78,61 @@ def test_history(): include_latest=True) expected = [(1, 3, (hist[-1], "spam"))] \ + [(s, n, (c, None)) for (s, n, c) in newhist] - nt.assert_equal(list(gothist), expected) + assert list(gothist) == expected gothist = ip.history_manager.get_tail(2) expected = newhist[-3:-1] - nt.assert_equal(list(gothist), expected) + assert list(gothist) == expected # Check get_hist_search gothist = ip.history_manager.search("*test*") - nt.assert_equal(list(gothist), [(1,2,hist[1])] ) + assert list(gothist) == [(1, 2, hist[1])] gothist = ip.history_manager.search("*=*") - nt.assert_equal(list(gothist), - [(1, 1, hist[0]), - (1, 2, hist[1]), - (1, 3, hist[2]), - newhist[0], - newhist[2], - newhist[3]]) + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] gothist = ip.history_manager.search("*=*", n=4) - nt.assert_equal(list(gothist), - [(1, 3, hist[2]), - newhist[0], - newhist[2], - newhist[3]]) + assert list(gothist) == [ + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] gothist = ip.history_manager.search("*=*", unique=True) - nt.assert_equal(list(gothist), - [(1, 1, hist[0]), - (1, 2, hist[1]), - (1, 3, hist[2]), - newhist[2], - newhist[3]]) + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[2], + newhist[3], + ] gothist = ip.history_manager.search("*=*", unique=True, n=3) - nt.assert_equal(list(gothist), - [(1, 3, hist[2]), - newhist[2], - newhist[3]]) + assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]] gothist = ip.history_manager.search("b*", output=True) - nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) + assert list(gothist) == [(1, 3, (hist[2], "spam"))] # Cross testing: check that magic %save can get previous session. testfilename = (tmp_path / "test.py").resolve() ip.magic("save " + str(testfilename) + " ~1/1-3") - with io.open(testfilename, encoding='utf-8') as testfile: - nt.assert_equal(testfile.read(), - u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") + with io.open(testfilename, encoding="utf-8") as testfile: + assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n" # Duplicate line numbers - check that it doesn't crash, and # gets a new session ip.history_manager.store_inputs(1, "rogue") ip.history_manager.writeout_cache() - nt.assert_equal(ip.history_manager.session_number, 3) + assert ip.history_manager.session_number == 3 finally: # Ensure saving thread is shut down before we try to clean up the files ip.history_manager.save_thread.stop() @@ -158,14 +153,14 @@ def test_extract_hist_ranges(): (-7, 1, 6), (-10, 1, None)] actual = list(extract_hist_ranges(instr)) - nt.assert_equal(actual, expected) + assert actual == expected def test_extract_hist_ranges_empty_str(): instr = "" expected = [(0, 1, None)] # 0 == current session, None == to end actual = list(extract_hist_ranges(instr)) - nt.assert_equal(actual, expected) + assert actual == expected def test_magic_rerun(): @@ -173,14 +168,14 @@ def test_magic_rerun(): ip = get_ipython() ip.run_cell("a = 10", store_history=True) ip.run_cell("a += 1", store_history=True) - nt.assert_equal(ip.user_ns["a"], 11) + assert ip.user_ns["a"] == 11 ip.run_cell("%rerun", store_history=True) - nt.assert_equal(ip.user_ns["a"], 12) + assert ip.user_ns["a"] == 12 def test_timestamp_type(): ip = get_ipython() info = ip.history_manager.get_session_info() - nt.assert_true(isinstance(info[1], datetime)) + assert isinstance(info[1], datetime) def test_hist_file_config(): cfg = Config() @@ -188,7 +183,7 @@ def test_hist_file_config(): cfg.HistoryManager.hist_file = Path(tfile.name) try: hm = HistoryManager(shell=get_ipython(), config=cfg) - nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file) + assert hm.hist_file == cfg.HistoryManager.hist_file finally: try: Path(tfile.name).unlink() @@ -210,14 +205,14 @@ def test_histmanager_disabled(): cfg.HistoryManager.hist_file = hist_file try: ip.history_manager = HistoryManager(shell=ip, config=cfg) - hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"] + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] for i, h in enumerate(hist, start=1): ip.history_manager.store_inputs(i, h) - nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) + assert ip.history_manager.input_hist_raw == [""] + hist ip.history_manager.reset() ip.history_manager.end_session() finally: ip.history_manager = hist_manager_ori # hist_file should not be created - nt.assert_false(hist_file.exists()) + assert hist_file.exists() is False From 0ee38081386bd1dd495b20780fd51e9387c00100 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:21:20 +0200 Subject: [PATCH 1593/3726] [core][tests][hooks] Remove nose --- IPython/core/tests/test_hooks.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/IPython/core/tests/test_hooks.py b/IPython/core/tests/test_hooks.py index 35d3f315b74..bd37526a738 100644 --- a/IPython/core/tests/test_hooks.py +++ b/IPython/core/tests/test_hooks.py @@ -6,7 +6,6 @@ # Imports #----------------------------------------------------------------------------- -import nose.tools as nt from IPython.core.error import TryNext from IPython.core.hooks import CommandChainDispatcher @@ -38,27 +37,26 @@ def __call__(self): def test_command_chain_dispatcher_ff(): """Test two failing hooks""" - fail1 = Fail(u'fail1') - fail2 = Fail(u'fail2') - dp = CommandChainDispatcher([(0, fail1), - (10, fail2)]) + fail1 = Fail("fail1") + fail2 = Fail("fail2") + dp = CommandChainDispatcher([(0, fail1), (10, fail2)]) try: dp() except TryNext as e: - nt.assert_equal(str(e), u'fail2') + assert str(e) == "fail2" else: assert False, "Expected exception was not raised." - nt.assert_true(fail1.called) - nt.assert_true(fail2.called) + assert fail1.called is True + assert fail2.called is True def test_command_chain_dispatcher_fofo(): """Test a mixture of failing and succeeding hooks.""" - fail1 = Fail(u'fail1') - fail2 = Fail(u'fail2') - okay1 = Okay(u'okay1') - okay2 = Okay(u'okay2') + fail1 = Fail("fail1") + fail2 = Fail("fail2") + okay1 = Okay("okay1") + okay2 = Okay("okay2") dp = CommandChainDispatcher([(0, fail1), # (5, okay1), # add this later @@ -66,12 +64,12 @@ def test_command_chain_dispatcher_fofo(): (15, okay2)]) dp.add(okay1, 5) - nt.assert_equal(dp(), u'okay1') + assert dp() == "okay1" - nt.assert_true(fail1.called) - nt.assert_true(okay1.called) - nt.assert_false(fail2.called) - nt.assert_false(okay2.called) + assert fail1.called is True + assert okay1.called is True + assert fail2.called is False + assert okay2.called is False def test_command_chain_dispatcher_eq_priority(): okay1 = Okay(u'okay1') From 8903f79e7ef56734ddfd8fb0fa552a69654afb78 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:27:28 +0200 Subject: [PATCH 1594/3726] [core][tests][inputsplitter] Remove nose --- IPython/core/tests/test_inputsplitter.py | 105 +++++++++++------------ 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index a39943aed80..148c5f043e4 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -8,8 +8,6 @@ import unittest import sys -import nose.tools as nt - from IPython.core import inputsplitter as isp from IPython.core.inputtransformer import InputTransformer from IPython.core.tests.test_inputtransformer import syntax, syntax_ml @@ -98,10 +96,10 @@ def test_remove_comments(): def test_get_input_encoding(): encoding = isp.get_input_encoding() - nt.assert_true(isinstance(encoding, str)) + assert isinstance(encoding, str) # simple-minded check that at least encoding a simple string works with the # encoding we got. - nt.assert_equal(u'test'.encode(encoding), b'test') + assert "test".encode(encoding) == b"test" class NoInputEncodingTestCase(unittest.TestCase): @@ -419,7 +417,7 @@ def test_syntax_multiline(self): for lraw, out_t_part in line_pairs: if out_t_part is not None: out_t_parts.append(out_t_part) - + if lraw is not None: isp.push(lraw) raw_parts.append(lraw) @@ -442,7 +440,7 @@ def test_syntax_multiline_cell(self): out = isp.transform_cell(raw) # Match ignoring trailing whitespace self.assertEqual(out.rstrip(), out_t.rstrip()) - + def test_cellmagic_preempt(self): isp = self.isp for raw, name, line, cell in [ @@ -464,17 +462,17 @@ def test_multiline_passthrough(self): class CommentTransformer(InputTransformer): def __init__(self): self._lines = [] - + def push(self, line): self._lines.append(line + '#') - + def reset(self): text = '\n'.join(self._lines) self._lines = [] return text - + isp.physical_line_transforms.insert(0, CommentTransformer()) - + for raw, expected in [ ("a=5", "a=5#"), ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')), @@ -527,38 +525,38 @@ def reset(self): # Tests for cell magics support def test_last_blank(): - nt.assert_false(isp.last_blank('')) - nt.assert_false(isp.last_blank('abc')) - nt.assert_false(isp.last_blank('abc\n')) - nt.assert_false(isp.last_blank('abc\na')) + assert isp.last_blank("") is False + assert isp.last_blank("abc") is False + assert isp.last_blank("abc\n") is False + assert isp.last_blank("abc\na") is False - nt.assert_true(isp.last_blank('\n')) - nt.assert_true(isp.last_blank('\n ')) - nt.assert_true(isp.last_blank('abc\n ')) - nt.assert_true(isp.last_blank('abc\n\n')) - nt.assert_true(isp.last_blank('abc\nd\n\n')) - nt.assert_true(isp.last_blank('abc\nd\ne\n\n')) - nt.assert_true(isp.last_blank('abc \n \n \n\n')) + assert isp.last_blank("\n") is True + assert isp.last_blank("\n ") is True + assert isp.last_blank("abc\n ") is True + assert isp.last_blank("abc\n\n") is True + assert isp.last_blank("abc\nd\n\n") is True + assert isp.last_blank("abc\nd\ne\n\n") is True + assert isp.last_blank("abc \n \n \n\n") is True def test_last_two_blanks(): - nt.assert_false(isp.last_two_blanks('')) - nt.assert_false(isp.last_two_blanks('abc')) - nt.assert_false(isp.last_two_blanks('abc\n')) - nt.assert_false(isp.last_two_blanks('abc\n\na')) - nt.assert_false(isp.last_two_blanks('abc\n \n')) - nt.assert_false(isp.last_two_blanks('abc\n\n')) - - nt.assert_true(isp.last_two_blanks('\n\n')) - nt.assert_true(isp.last_two_blanks('\n\n ')) - nt.assert_true(isp.last_two_blanks('\n \n')) - nt.assert_true(isp.last_two_blanks('abc\n\n ')) - nt.assert_true(isp.last_two_blanks('abc\n\n\n')) - nt.assert_true(isp.last_two_blanks('abc\n\n \n')) - nt.assert_true(isp.last_two_blanks('abc\n\n \n ')) - nt.assert_true(isp.last_two_blanks('abc\n\n \n \n')) - nt.assert_true(isp.last_two_blanks('abc\nd\n\n\n')) - nt.assert_true(isp.last_two_blanks('abc\nd\ne\nf\n\n\n')) + assert isp.last_two_blanks("") is False + assert isp.last_two_blanks("abc") is False + assert isp.last_two_blanks("abc\n") is False + assert isp.last_two_blanks("abc\n\na") is False + assert isp.last_two_blanks("abc\n \n") is False + assert isp.last_two_blanks("abc\n\n") is False + + assert isp.last_two_blanks("\n\n") is True + assert isp.last_two_blanks("\n\n ") is True + assert isp.last_two_blanks("\n \n") is True + assert isp.last_two_blanks("abc\n\n ") is True + assert isp.last_two_blanks("abc\n\n\n") is True + assert isp.last_two_blanks("abc\n\n \n") is True + assert isp.last_two_blanks("abc\n\n \n ") is True + assert isp.last_two_blanks("abc\n\n \n \n") is True + assert isp.last_two_blanks("abc\nd\n\n\n") is True + assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True class CellMagicsCommon(object): @@ -567,11 +565,11 @@ def test_whole_cell(self): src = "%%cellm line\nbody\n" out = self.sp.transform_cell(src) ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n" - nt.assert_equal(out, ref) - + assert out == ref + def test_cellmagic_help(self): self.sp.push('%%cellm?') - nt.assert_false(self.sp.push_accepts_more()) + assert self.sp.push_accepts_more() is False def tearDown(self): self.sp.reset() @@ -582,14 +580,14 @@ class CellModeCellMagics(CellMagicsCommon, unittest.TestCase): def test_incremental(self): sp = self.sp - sp.push('%%cellm firstline\n') - nt.assert_true(sp.push_accepts_more()) #1 - sp.push('line2\n') - nt.assert_true(sp.push_accepts_more()) #2 - sp.push('\n') + sp.push("%%cellm firstline\n") + assert sp.push_accepts_more() is True # 1 + sp.push("line2\n") + assert sp.push_accepts_more() is True # 2 + sp.push("\n") # This should accept a blank line and carry on until the cell is reset - nt.assert_true(sp.push_accepts_more()) #3 - + assert sp.push_accepts_more() is True # 3 + def test_no_strip_coding(self): src = '\n'.join([ '%%writefile foo.py', @@ -597,7 +595,7 @@ def test_no_strip_coding(self): 'print(u"üñîçø∂é")', ]) out = self.sp.transform_cell(src) - nt.assert_in('# coding: utf-8', out) + assert "# coding: utf-8" in out class LineModeCellMagics(CellMagicsCommon, unittest.TestCase): @@ -605,11 +603,12 @@ class LineModeCellMagics(CellMagicsCommon, unittest.TestCase): def test_incremental(self): sp = self.sp - sp.push('%%cellm line2\n') - nt.assert_true(sp.push_accepts_more()) #1 - sp.push('\n') + sp.push("%%cellm line2\n") + assert sp.push_accepts_more() is True # 1 + sp.push("\n") # In this case, a blank line should end the cell magic - nt.assert_false(sp.push_accepts_more()) #2 + assert sp.push_accepts_more() is False # 2 + indentation_samples = [ ('a = 1', 0), From 3bf77e7cb1d0b24e2954cff0eb789ae352b1ba9b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:28:39 +0200 Subject: [PATCH 1595/3726] [core][tests][inputtransformer] Remove nose --- IPython/core/tests/test_inputtransformer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py index be8209e6d02..4de97b87cb8 100644 --- a/IPython/core/tests/test_inputtransformer.py +++ b/IPython/core/tests/test_inputtransformer.py @@ -1,5 +1,4 @@ import tokenize -import nose.tools as nt from IPython.testing import tools as tt @@ -25,7 +24,7 @@ def transform_checker(tests, transformer, **kwargs): out = transformer.reset() else: out = transformer.push(inp) - nt.assert_equal(out, tr) + assert out == tr finally: transformer.reset() From b75105dfa054175c0834edcd140530432327092b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:29:52 +0200 Subject: [PATCH 1596/3726] [core][tests][inputtransformer2] Remove nose --- IPython/core/tests/test_inputtransformer2.py | 96 ++++++++++---------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index cd7c5311e45..d341ec737ab 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -4,7 +4,6 @@ more complex. See test_inputtransformer2_line for tests for line-based transformations. """ -import nose.tools as nt import string from IPython.core import inputtransformer2 as ipt2 @@ -147,9 +146,10 @@ def check_make_token_by_line_never_ends_empty(): """ from string import printable for c in printable: - nt.assert_not_equal(make_tokens_by_line(c)[-1], []) + assert make_tokens_by_line(c)[-1] != [] for k in printable: - nt.assert_not_equal(make_tokens_by_line(c+k)[-1], []) + assert make_tokens_by_line(c + k)[-1] != [] + def check_find(transformer, case, match=True): sample, expected_start, _ = case @@ -157,21 +157,21 @@ def check_find(transformer, case, match=True): res = transformer.find(tbl) if match: # start_line is stored 0-indexed, expected values are 1-indexed - nt.assert_equal((res.start_line+1, res.start_col), expected_start) + assert (res.start_line + 1, res.start_col) == expected_start return res else: - nt.assert_is(res, None) + assert res is None def check_transform(transformer_cls, case): lines, start, expected = case transformer = transformer_cls(start) - nt.assert_equal(transformer.transform(lines), expected) + assert transformer.transform(lines) == expected def test_continued_line(): lines = MULTILINE_MAGIC_ASSIGN[0] - nt.assert_equal(ipt2.find_end_of_continued_line(lines, 1), 2) + assert ipt2.find_end_of_continued_line(lines, 1) == 2 - nt.assert_equal(ipt2.assemble_continued_line(lines, (1, 5), 2), "foo bar") + assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar" def test_find_assign_magic(): check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) @@ -217,12 +217,12 @@ def test_find_help(): check_find(ipt2.HelpEnd, case) tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE) - nt.assert_equal(tf.q_line, 1) - nt.assert_equal(tf.q_col, 3) + assert tf.q_line == 1 + assert tf.q_col == 3 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE) - nt.assert_equal(tf.q_line, 1) - nt.assert_equal(tf.q_col, 8) + assert tf.q_line == 1 + assert tf.q_col == 8 # ? in a comment does not trigger help check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False) @@ -231,16 +231,16 @@ def test_find_help(): def test_transform_help(): tf = ipt2.HelpEnd((1, 0), (1, 9)) - nt.assert_equal(tf.transform(HELP_IN_EXPR[0]), HELP_IN_EXPR[2]) + assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2] tf = ipt2.HelpEnd((1, 0), (2, 3)) - nt.assert_equal(tf.transform(HELP_CONTINUED_LINE[0]), HELP_CONTINUED_LINE[2]) + assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2] tf = ipt2.HelpEnd((1, 0), (2, 8)) - nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2]) + assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2] tf = ipt2.HelpEnd((1, 0), (1, 0)) - nt.assert_equal(tf.transform(HELP_UNICODE[0]), HELP_UNICODE[2]) + assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2] def test_find_assign_op_dedent(): """ @@ -250,31 +250,34 @@ class Tk: def __init__(self, s): self.string = s - nt.assert_equal(_find_assign_op([Tk(s) for s in ('','a','=','b')]), 2) - nt.assert_equal(_find_assign_op([Tk(s) for s in ('','(', 'a','=','b', ')', '=' ,'5')]), 6) + assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2 + assert ( + _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6 + ) + def test_check_complete(): cc = ipt2.TransformerManager().check_complete - nt.assert_equal(cc("a = 1"), ("complete", None)) - nt.assert_equal(cc("for a in range(5):"), ("incomplete", 4)) - nt.assert_equal(cc("for a in range(5):\n if a > 0:"), ("incomplete", 8)) - nt.assert_equal(cc("raise = 2"), ("invalid", None)) - nt.assert_equal(cc("a = [1,\n2,"), ("incomplete", 0)) - nt.assert_equal(cc("(\n))"), ("incomplete", 0)) - nt.assert_equal(cc("\\\r\n"), ("incomplete", 0)) - nt.assert_equal(cc("a = '''\n hi"), ("incomplete", 3)) - nt.assert_equal(cc("def a():\n x=1\n global x"), ("invalid", None)) - nt.assert_equal(cc("a \\ "), ("invalid", None)) # Nothing allowed after backslash - nt.assert_equal(cc("1\\\n+2"), ("complete", None)) - nt.assert_equal(cc("exit"), ("complete", None)) + assert cc("a = 1") == ("complete", None) + assert cc("for a in range(5):") == ("incomplete", 4) + assert cc("for a in range(5):\n if a > 0:") == ("incomplete", 8) + assert cc("raise = 2") == ("invalid", None) + assert cc("a = [1,\n2,") == ("incomplete", 0) + assert cc("(\n))") == ("incomplete", 0) + assert cc("\\\r\n") == ("incomplete", 0) + assert cc("a = '''\n hi") == ("incomplete", 3) + assert cc("def a():\n x=1\n global x") == ("invalid", None) + assert cc("a \\ ") == ("invalid", None) # Nothing allowed after backslash + assert cc("1\\\n+2") == ("complete", None) + assert cc("exit") == ("complete", None) example = dedent(""" if True: a=1""" ) - nt.assert_equal(cc(example), ('incomplete', 4)) - nt.assert_equal(cc(example+'\n'), ('complete', None)) - nt.assert_equal(cc(example+'\n '), ('complete', None)) + assert cc(example) == ("incomplete", 4) + assert cc(example + "\n") == ("complete", None) + assert cc(example + "\n ") == ("complete", None) # no need to loop on all the letters/numbers. short = '12abAB'+string.printable[62:] @@ -284,7 +287,8 @@ def test_check_complete(): for k in short: cc(c+k) - nt.assert_equal(cc("def f():\n x=0\n \\\n "), ('incomplete', 2)) + assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2) + def test_check_complete_II(): """ @@ -294,7 +298,7 @@ def test_check_complete_II(): """ cc = ipt2.TransformerManager().check_complete - nt.assert_equal(cc('''def foo():\n """'''), ('incomplete', 4)) + assert cc('''def foo():\n """''') == ("incomplete", 4) def test_check_complete_invalidates_sunken_brackets(): @@ -303,22 +307,22 @@ def test_check_complete_invalidates_sunken_brackets(): interpreted as invalid """ cc = ipt2.TransformerManager().check_complete - nt.assert_equal(cc(")"), ("invalid", None)) - nt.assert_equal(cc("]"), ("invalid", None)) - nt.assert_equal(cc("}"), ("invalid", None)) - nt.assert_equal(cc(")("), ("invalid", None)) - nt.assert_equal(cc("]["), ("invalid", None)) - nt.assert_equal(cc("}{"), ("invalid", None)) - nt.assert_equal(cc("]()("), ("invalid", None)) - nt.assert_equal(cc("())("), ("invalid", None)) - nt.assert_equal(cc(")[]("), ("invalid", None)) - nt.assert_equal(cc("()]("), ("invalid", None)) + assert cc(")") == ("invalid", None) + assert cc("]") == ("invalid", None) + assert cc("}") == ("invalid", None) + assert cc(")(") == ("invalid", None) + assert cc("][") == ("invalid", None) + assert cc("}{") == ("invalid", None) + assert cc("]()(") == ("invalid", None) + assert cc("())(") == ("invalid", None) + assert cc(")[](") == ("invalid", None) + assert cc("()](") == ("invalid", None) def test_null_cleanup_transformer(): manager = ipt2.TransformerManager() manager.cleanup_transforms.insert(0, null_cleanup_transformer) - assert manager.transform_cell("") == "" + assert manager.transform_cell("") == "" From 85ad41c0709f187419461c33c02ec0bd16b0a32e Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:33:14 +0200 Subject: [PATCH 1597/3726] [core][tests][interactiveshell] Remove nose --- IPython/core/tests/test_interactiveshell.py | 128 ++++++++++---------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 9092ce5b455..ef7d69ff8be 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -21,8 +21,6 @@ from os.path import join -import nose.tools as nt - from IPython.core.error import InputRejected from IPython.core.inputtransformer import InputTransformer from IPython.core import interactiveshell @@ -220,33 +218,39 @@ def test_var_expand(self): def test_var_expand_local(self): """Test local variable expansion in !system and %magic calls""" # !system - ip.run_cell('def test():\n' - ' lvar = "ttt"\n' - ' ret = !echo {lvar}\n' - ' return ret[0]\n') - res = ip.user_ns['test']() - nt.assert_in('ttt', res) - + ip.run_cell( + "def test():\n" + ' lvar = "ttt"\n' + " ret = !echo {lvar}\n" + " return ret[0]\n" + ) + res = ip.user_ns["test"]() + self.assertIn("ttt", res) + # %magic - ip.run_cell('def makemacro():\n' - ' macroname = "macro_var_expand_locals"\n' - ' %macro {macroname} codestr\n') - ip.user_ns['codestr'] = "str(12)" - ip.run_cell('makemacro()') - nt.assert_in('macro_var_expand_locals', ip.user_ns) - + ip.run_cell( + "def makemacro():\n" + ' macroname = "macro_var_expand_locals"\n' + " %macro {macroname} codestr\n" + ) + ip.user_ns["codestr"] = "str(12)" + ip.run_cell("makemacro()") + self.assertIn("macro_var_expand_locals", ip.user_ns) + def test_var_expand_self(self): """Test variable expansion with the name 'self', which was failing. See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 """ - ip.run_cell('class cTest:\n' - ' classvar="see me"\n' - ' def test(self):\n' - ' res = !echo Variable: {self.classvar}\n' - ' return res[0]\n') - nt.assert_in('see me', ip.user_ns['cTest']().test()) - + ip.run_cell( + "class cTest:\n" + ' classvar="see me"\n' + " def test(self):\n" + " res = !echo Variable: {self.classvar}\n" + " return res[0]\n" + ) + self.assertIn("see me", ip.user_ns["cTest"]().test()) + def test_bad_var_expand(self): """var_expand on invalid formats shouldn't raise""" # SyntaxError @@ -346,7 +350,7 @@ def lmagic(line): info = dict(found=True, isalias=False, ismagic=True, namespace = 'IPython internal', obj= lmagic.__wrapped__, parent = None) - nt.assert_equal(lfind, info) + self.assertEqual(lfind, info) def test_ofind_cell_magic(self): from IPython.core.magic import register_cell_magic @@ -360,7 +364,7 @@ def cmagic(line, cell): info = dict(found=True, isalias=False, ismagic=True, namespace = 'IPython internal', obj= cmagic.__wrapped__, parent = None) - nt.assert_equal(find, info) + self.assertEqual(find, info) def test_ofind_property_with_error(self): class A(object): @@ -372,7 +376,7 @@ def foo(self): found = ip._ofind('a.foo', [('locals', locals())]) info = dict(found=True, isalias=False, ismagic=False, namespace='locals', obj=A.foo, parent=a) - nt.assert_equal(found, info) + self.assertEqual(found, info) def test_ofind_multiple_attribute_lookups(self): class A(object): @@ -387,7 +391,7 @@ def foo(self): found = ip._ofind('a.a.a.foo', [('locals', locals())]) info = dict(found=True, isalias=False, ismagic=False, namespace='locals', obj=A.foo, parent=a.a.a) - nt.assert_equal(found, info) + self.assertEqual(found, info) def test_ofind_slotted_attributes(self): class A(object): @@ -399,12 +403,12 @@ def __init__(self): found = ip._ofind('a.foo', [('locals', locals())]) info = dict(found=True, isalias=False, ismagic=False, namespace='locals', obj=a.foo, parent=a) - nt.assert_equal(found, info) + self.assertEqual(found, info) found = ip._ofind('a.bar', [('locals', locals())]) info = dict(found=False, isalias=False, ismagic=False, namespace=None, obj=None, parent=a) - nt.assert_equal(found, info) + self.assertEqual(found, info) def test_ofind_prefers_property_to_instance_level_attribute(self): class A(object): @@ -412,10 +416,10 @@ class A(object): def foo(self): return 'bar' a = A() - a.__dict__['foo'] = 'baz' - nt.assert_equal(a.foo, 'bar') - found = ip._ofind('a.foo', [('locals', locals())]) - nt.assert_is(found['obj'], A.foo) + a.__dict__["foo"] = "baz" + self.assertEqual(a.foo, "bar") + found = ip._ofind("a.foo", [("locals", locals())]) + self.assertIs(found["obj"], A.foo) def test_custom_syntaxerror_exception(self): called = [] @@ -819,7 +823,7 @@ def test_unregistering(self): ip.run_cell("1 + 2") # This should have been removed. - nt.assert_not_in(err_transformer, ip.ast_transformers) + self.assertNotIn(err_transformer, ip.ast_transformers) class StringRejector(ast.NodeTransformer): @@ -889,21 +893,21 @@ def test_user_variables(): keys = {'dummy', 'doesnotexist'} r = ip.user_expressions({ key:key for key in keys}) - nt.assert_equal(keys, set(r.keys())) - dummy = r['dummy'] - nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys())) - nt.assert_equal(dummy['status'], 'ok') - data = dummy['data'] - metadata = dummy['metadata'] - nt.assert_equal(data.get('text/html'), d._repr_html_()) + assert keys == set(r.keys()) + dummy = r["dummy"] + assert {"status", "data", "metadata"} == set(dummy.keys()) + assert dummy["status"] == "ok" + data = dummy["data"] + metadata = dummy["metadata"] + assert data.get("text/html") == d._repr_html_() js, jsmd = d._repr_javascript_() - nt.assert_equal(data.get('application/javascript'), js) - nt.assert_equal(metadata.get('application/javascript'), jsmd) - - dne = r['doesnotexist'] - nt.assert_equal(dne['status'], 'error') - nt.assert_equal(dne['ename'], 'NameError') - + assert data.get("application/javascript") == js + assert metadata.get("application/javascript") == jsmd + + dne = r["doesnotexist"] + assert dne["status"] == "error" + assert dne["ename"] == "NameError" + # back to text only ip.display_formatter.active_types = ['text/plain'] @@ -917,18 +921,18 @@ def test_user_expression(): r = ip.user_expressions(query) import pprint pprint.pprint(r) - nt.assert_equal(set(r.keys()), set(query.keys())) - a = r['a'] - nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys())) - nt.assert_equal(a['status'], 'ok') - data = a['data'] - metadata = a['metadata'] - nt.assert_equal(data.get('text/plain'), '3') - - b = r['b'] - nt.assert_equal(b['status'], 'error') - nt.assert_equal(b['ename'], 'ZeroDivisionError') - + assert set(r.keys()) == set(query.keys()) + a = r["a"] + assert {"status", "data", "metadata"} == set(a.keys()) + assert a["status"] == "ok" + data = a["data"] + metadata = a["metadata"] + assert data.get("text/plain") == "3" + + b = r["b"] + assert b["status"] == "error" + assert b["ename"] == "ZeroDivisionError" + # back to text only ip.display_formatter.active_types = ['text/plain'] @@ -1030,8 +1034,8 @@ def test_custom_exc_count(): ip.run_cell("def foo()", store_history=True) # restore default excepthook ip.set_custom_exc((), None) - nt.assert_equal(hook.call_count, 1) - nt.assert_equal(ip.execution_count, before + 1) + assert hook.call_count == 1 + assert ip.execution_count == before + 1 def test_run_cell_async(): From 9567fb34a8b59abf8b371e91555d941af03cea5b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:41:39 +0200 Subject: [PATCH 1598/3726] [core][tests][magic] Remove nose --- IPython/core/tests/test_magic.py | 471 ++++++++++++++++--------------- 1 file changed, 248 insertions(+), 223 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index d534d11018b..37432ca309d 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -17,8 +17,6 @@ from textwrap import dedent from unittest import TestCase, mock -import nose.tools as nt - import pytest from IPython import get_ipython @@ -47,18 +45,20 @@ class DummyMagics(magic.Magics): pass def test_extract_code_ranges(): instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" - expected = [(0, 1), - (2, 3), - (4, 6), - (6, 9), - (9, 14), - (16, None), - (None, 9), - (9, None), - (None, 13), - (None, None)] + expected = [ + (0, 1), + (2, 3), + (4, 6), + (6, 9), + (9, 14), + (16, None), + (None, 9), + (9, None), + (None, 13), + (None, None), + ] actual = list(code.extract_code_ranges(instr)) - nt.assert_equal(actual, expected) + assert actual == expected def test_extract_symbols(): source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" @@ -70,7 +70,7 @@ def test_extract_symbols(): (["class A: pass\n"], ['a']), ([], ['z'])] for symbols, exp in zip(symbols_args, expected): - nt.assert_equal(code.extract_symbols(source, symbols), exp) + assert code.extract_symbols(source, symbols) == exp def test_extract_symbols_raises_exception_with_non_python_code(): @@ -78,13 +78,13 @@ def test_extract_symbols_raises_exception_with_non_python_code(): "def hello\n" "puts 'Hello world'\n" "end") - with nt.assert_raises(SyntaxError): + with pytest.raises(SyntaxError): code.extract_symbols(source, "hello") def test_magic_not_found(): # magic not found raises UsageError - with nt.assert_raises(UsageError): + with pytest.raises(UsageError): _ip.magic('doesntexist') # ensure result isn't success when a magic isn't found @@ -94,7 +94,7 @@ def test_magic_not_found(): def test_cell_magic_not_found(): # magic not found raises UsageError - with nt.assert_raises(UsageError): + with pytest.raises(UsageError): _ip.run_cell_magic('doesntexist', 'line', 'cell') # ensure result isn't success when a magic isn't found @@ -127,7 +127,7 @@ def test_config_available_configs(): stdout = captured.stdout config_classes = stdout.strip().split('\n')[1:] - nt.assert_list_equal(config_classes, sorted(set(config_classes))) + assert config_classes == sorted(set(config_classes)) def test_config_print_class(): """ test that config with a classname prints the class's options. """ @@ -144,19 +144,18 @@ def test_rehashx(): # clear up everything _ip.alias_manager.clear_aliases() del _ip.db['syscmdlist'] - + _ip.magic('rehashx') # Practically ALL ipython development systems will have more than 10 aliases - nt.assert_true(len(_ip.alias_manager.aliases) > 10) + assert len(_ip.alias_manager.aliases) > 10 for name, cmd in _ip.alias_manager.aliases: # we must strip dots from alias names - nt.assert_not_in('.', name) + assert "." not in name # rehashx must fill up syscmdlist scoms = _ip.db['syscmdlist'] - nt.assert_true(len(scoms) > 10) - + assert len(scoms) > 10 def test_magic_parse_options(): @@ -170,16 +169,17 @@ def test_magic_parse_options(): expected = 'c:x' else: expected = path - nt.assert_equal(opts['f'], expected) + assert opts["f"] == expected + def test_magic_parse_long_options(): """Magic.parse_options can handle --foo=bar long options""" ip = get_ipython() m = DummyMagics(ip) - opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=') - nt.assert_in('foo', opts) - nt.assert_in('bar', opts) - nt.assert_equal(opts['bar'], "bubble") + opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") + assert "foo" in opts + assert "bar" in opts + assert opts["bar"] == "bubble" def doctest_hist_f(): @@ -200,24 +200,24 @@ def doctest_hist_op(): In [1]: class b(float): ...: pass - ...: + ...: In [2]: class s(object): ...: def __str__(self): ...: return 's' - ...: + ...: - In [3]: + In [3]: In [4]: class r(b): ...: def __repr__(self): ...: return 'r' - ...: + ...: In [5]: class sr(s,r): pass - ...: + ...: - In [6]: + In [6]: In [7]: bb=b() @@ -233,23 +233,23 @@ def doctest_hist_op(): In [12]: str(ss) Out[12]: 's' - In [13]: + In [13]: In [14]: %hist -op >>> class b: ... pass - ... + ... >>> class s(b): ... def __str__(self): ... return 's' - ... - >>> + ... + >>> >>> class r(b): ... def __repr__(self): ... return 'r' - ... + ... >>> class sr(s,r): pass - >>> + >>> >>> bb=b() >>> ss=s() >>> rr=r() @@ -258,12 +258,12 @@ def doctest_hist_op(): 4.5 >>> str(ss) 's' - >>> + >>> """ def test_hist_pof(): ip = get_ipython() - ip.run_cell(u"1+2", store_history=True) + ip.run_cell("1+2", store_history=True) #raise Exception(ip.history_manager.session_number) #raise Exception(list(ip.history_manager._get_range_session())) with TemporaryDirectory() as td: @@ -279,10 +279,10 @@ def test_macro(): for i, cmd in enumerate(cmds, start=1): ip.history_manager.store_inputs(i, cmd) ip.magic("macro test 1-3") - nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n") - + assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" + # List macros - nt.assert_in("test", ip.magic("macro")) + assert "test" in ip.magic("macro") def test_macro_run(): @@ -292,7 +292,7 @@ def test_macro_run(): cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] for cmd in cmds: ip.run_cell(cmd, store_history=True) - nt.assert_equal(ip.user_ns["test"].value, "a+=1\nprint(a)\n") + assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" with tt.AssertPrints("12"): ip.run_cell("test") with tt.AssertPrints("13"): @@ -304,56 +304,61 @@ def test_magic_magic(): ip = get_ipython() with capture_output() as captured: ip.magic("magic") - + stdout = captured.stdout - nt.assert_in('%magic', stdout) - nt.assert_in('IPython', stdout) - nt.assert_in('Available', stdout) + assert "%magic" in stdout + assert "IPython" in stdout + assert "Available" in stdout @dec.skipif_not_numpy def test_numpy_reset_array_undec(): "Test '%reset array' functionality" - _ip.ex('import numpy as np') - _ip.ex('a = np.empty(2)') - nt.assert_in('a', _ip.user_ns) - _ip.magic('reset -f array') - nt.assert_not_in('a', _ip.user_ns) + _ip.ex("import numpy as np") + _ip.ex("a = np.empty(2)") + assert "a" in _ip.user_ns + _ip.magic("reset -f array") + assert "a" not in _ip.user_ns + def test_reset_out(): "Test '%reset out' magic" _ip.run_cell("parrot = 'dead'", store_history=True) # test '%reset -f out', make an Out prompt _ip.run_cell("parrot", store_history=True) - nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) - _ip.magic('reset -f out') - nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')]) - nt.assert_equal(len(_ip.user_ns['Out']), 0) + assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] + _ip.magic("reset -f out") + assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] + assert len(_ip.user_ns["Out"]) == 0 + def test_reset_in(): "Test '%reset in' magic" # test '%reset -f in' _ip.run_cell("parrot", store_history=True) - nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) - _ip.magic('%reset -f in') - nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')]) - nt.assert_equal(len(set(_ip.user_ns['In'])), 1) + assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + _ip.magic("%reset -f in") + assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + assert len(set(_ip.user_ns["In"])) == 1 + def test_reset_dhist(): "Test '%reset dhist' magic" - _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing - _ip.magic('cd ' + os.path.dirname(nt.__file__)) - _ip.magic('cd -') - nt.assert_true(len(_ip.user_ns['_dh']) > 0) - _ip.magic('reset -f dhist') - nt.assert_equal(len(_ip.user_ns['_dh']), 0) - _ip.run_cell("_dh = [d for d in tmp]") #restore + _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing + _ip.magic("cd " + os.path.dirname(pytest.__file__)) + _ip.magic("cd -") + assert len(_ip.user_ns["_dh"]) > 0 + _ip.magic("reset -f dhist") + assert len(_ip.user_ns["_dh"]) == 0 + _ip.run_cell("_dh = [d for d in tmp]") # restore + def test_reset_in_length(): "Test that '%reset in' preserves In[] length" _ip.run_cell("print 'foo'") _ip.run_cell("reset -f in") - nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1) + assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 + class TestResetErrors(TestCase): @@ -384,7 +389,7 @@ def test_tb_syntaxerror(): """test %tb after a SyntaxError""" ip = get_ipython() ip.run_cell("for") - + # trap and validate stdout save_stdout = sys.stdout try: @@ -395,18 +400,18 @@ def test_tb_syntaxerror(): sys.stdout = save_stdout # trim output, and only check the last line last_line = out.rstrip().splitlines()[-1].strip() - nt.assert_equal(last_line, "SyntaxError: invalid syntax") + assert last_line == "SyntaxError: invalid syntax" def test_time(): ip = get_ipython() - + with tt.AssertPrints("Wall time: "): ip.run_cell("%time None") - + ip.run_cell("def f(kmjy):\n" " %time print (2*kmjy)") - + with tt.AssertPrints("Wall time: "): with tt.AssertPrints("hihi", suppress=False): ip.run_cell("f('hi')") @@ -419,12 +424,12 @@ def test_time_last_not_expression(): del ip.user_ns['var_1'] assert ip.user_ns['var_2'] == 2 del ip.user_ns['var_2'] - + @dec.skip_win32 def test_time2(): ip = get_ipython() - + with tt.AssertPrints("CPU times: user "): ip.run_cell("%time None") @@ -432,7 +437,7 @@ def test_time3(): """Erroneous magic function calls, issue gh-3334""" ip = get_ipython() ip.user_ns.pop('run', None) - + with tt.AssertNotPrints("not found", channel='stderr'): ip.run_cell("%%time\n" "run = 0\n" @@ -448,18 +453,21 @@ def test_multiline_time(): a = "ho" b = "hey" a+b - """)) - nt.assert_equal(ip.user_ns_hidden['_'], 'hohey') + """ + ) + ) + assert ip.user_ns_hidden["_"] == "hohey" + def test_time_local_ns(): """ Test that local_ns is actually global_ns when running a cell magic """ ip = get_ipython() - ip.run_cell("%%time\n" - "myvar = 1") - nt.assert_equal(ip.user_ns['myvar'], 1) - del ip.user_ns['myvar'] + ip.run_cell("%%time\n" "myvar = 1") + assert ip.user_ns["myvar"] == 1 + del ip.user_ns["myvar"] + def test_doctest_mode(): "Toggle doctest_mode twice, it should be a no-op and run without error" @@ -472,8 +480,8 @@ def test_parse_options(): # These are only the most minimal of tests, more should be added later. At # the very least we check that basic text/unicode calls work OK. m = DummyMagics(_ip) - nt.assert_equal(m.parse_options('foo', '')[1], 'foo') - nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo') + assert m.parse_options("foo", "")[1] == "foo" + assert m.parse_options("foo", "")[1] == "foo" def test_parse_options_preserve_non_option_string(): @@ -482,8 +490,8 @@ def test_parse_options_preserve_non_option_string(): opts, stmt = m.parse_options( " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True ) - nt.assert_equal(opts, {"n": "1", "r": "13"}) - nt.assert_equal(stmt, "_ = 314 + foo") + assert opts == {"n": "1", "r": "13"} + assert stmt == "_ = 314 + foo" def test_run_magic_preserve_code_block(): @@ -501,13 +509,13 @@ def test_dirops(): ipdir = os.path.realpath(_ip.ipython_dir) try: _ip.magic('cd "%s"' % ipdir) - nt.assert_equal(curpath(), ipdir) + assert curpath() == ipdir _ip.magic('cd -') - nt.assert_equal(curpath(), startdir) + assert curpath() == startdir _ip.magic('pushd "%s"' % ipdir) - nt.assert_equal(curpath(), ipdir) + assert curpath() == ipdir _ip.magic('popd') - nt.assert_equal(curpath(), startdir) + assert curpath() == startdir finally: os.chdir(startdir) @@ -534,8 +542,8 @@ def test_xmode(): xmode = _ip.InteractiveTB.mode for i in range(4): _ip.magic("xmode") - nt.assert_equal(_ip.InteractiveTB.mode, xmode) - + assert _ip.InteractiveTB.mode == xmode + def test_reset_hard(): monitor = [] class A(object): @@ -543,14 +551,14 @@ def __del__(self): monitor.append(1) def __repr__(self): return "" - + _ip.user_ns["a"] = A() _ip.run_cell("a") - - nt.assert_equal(monitor, []) + + assert monitor == [] _ip.magic("reset -f") - nt.assert_equal(monitor, [1]) - + assert monitor == [1] + class TestXdel(tt.TempFileMixin): def test_xdel(self): """Test that references from %run are cleared by xdel.""" @@ -564,36 +572,36 @@ def test_xdel(self): _ip.magic("run %s" % self.fname) # ... as does the displayhook. _ip.run_cell("a") - + monitor = _ip.user_ns["A"].monitor - nt.assert_equal(monitor, []) - + assert monitor == [] + _ip.magic("xdel a") - + # Check that a's __del__ method has been called. - nt.assert_equal(monitor, [1]) + assert monitor == [1] def doctest_who(): """doctest for %who - + In [1]: %reset -f - + In [2]: alpha = 123 - + In [3]: beta = 'beta' - + In [4]: %who int alpha - + In [5]: %who str beta - + In [6]: %whos Variable Type Data/Info ---------------------------- alpha int 123 beta str beta - + In [7]: %who_ls Out[7]: ['alpha', 'beta'] """ @@ -608,25 +616,25 @@ def __repr__(self): def doctest_precision(): """doctest for %precision - + In [1]: f = get_ipython().display_formatter.formatters['text/plain'] - + In [2]: %precision 5 Out[2]: '%.5f' - + In [3]: f.float_format Out[3]: '%.5f' - + In [4]: %precision %e Out[4]: '%e' - + In [5]: f(3.1415927) Out[5]: '3.141593e+00' """ def test_debug_magic(): """Test debugging a small code with %debug - + In [1]: with PdbTestInput(['c']): ...: %debug print("a b") #doctest: +ELLIPSIS ...: @@ -661,11 +669,12 @@ def lmagic(line): ip.user_ns['lmagic_out'] = line # line mode test - _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line') - nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') + _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" # cell mode test - _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2') - nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') + _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + def test_timeit_return(): """ @@ -688,7 +697,7 @@ def test_timeit_return_quiet(): assert (res is not None) def test_timeit_invalid_return(): - with nt.assert_raises_regex(SyntaxError, "outside function"): + with pytest.raises(SyntaxError): _ip.run_line_magic('timeit', 'return') @dec.skipif(execution.profile is None) @@ -700,17 +709,19 @@ def lmagic(line): ip.user_ns['lmagic_out'] = line # line mode test - _ip.run_line_magic('prun', '-q %lmagic my line') - nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') + _ip.run_line_magic("prun", "-q %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" # cell mode test - _ip.run_cell_magic('prun', '-q', '%lmagic my line2') - nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') + _ip.run_cell_magic("prun", "-q", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + @dec.skipif(execution.profile is None) def test_prun_quotes(): "Test that prun does not clobber string escapes (GH #1302)" _ip.magic(r"prun -q x = '\t'") - nt.assert_equal(_ip.user_ns['x'], '\t') + assert _ip.user_ns["x"] == "\t" + def test_extension(): # Debugging information for failures of this test @@ -719,14 +730,14 @@ def test_extension(): print(' ', p) print('CWD', os.getcwd()) - nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension") + pytest.raises(ImportError, _ip.magic, "load_ext daft_extension") daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") sys.path.insert(0, daft_path) try: _ip.user_ns.pop('arq', None) invalidate_caches() # Clear import caches _ip.magic("load_ext daft_extension") - nt.assert_equal(_ip.user_ns['arq'], 185) + assert _ip.user_ns["arq"] == 185 _ip.magic("unload_ext daft_extension") assert 'arq' not in _ip.user_ns finally: @@ -736,7 +747,7 @@ def test_extension(): def test_notebook_export_json(): _ip = get_ipython() _ip.history_manager.reset() # Clear any existing history. - cmds = [u"a=1", u"def b():\n return a**2", u"print('noël, été', b())"] + cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] for i, cmd in enumerate(cmds, start=1): _ip.history_manager.store_inputs(i, cmd) with TemporaryDirectory() as td: @@ -797,11 +808,11 @@ class CellMagicTestCase(TestCase): def check_ident(self, magic): # Manually called, we get the result - out = _ip.run_cell_magic(magic, 'a', 'b') - nt.assert_equal(out, ('a','b')) + out = _ip.run_cell_magic(magic, "a", "b") + assert out == ("a", "b") # Via run_cell, it goes into the user's namespace via displayhook - _ip.run_cell('%%' + magic +' c\nd\n') - nt.assert_equal(_ip.user_ns['_'], ('c','d\n')) + _ip.run_cell("%%" + magic + " c\nd\n") + assert _ip.user_ns["_"] == ("c", "d\n") def test_cell_magic_func_deco(self): "Cell magic using simple decorator" @@ -839,12 +850,12 @@ class MyMagics2(Magics): @cell_magic('cellm4') def cellm33(self, line, cell): return line, cell - + _ip.register_magics(MyMagics2) self.check_ident('cellm4') # Check that nothing is registered as 'cellm33' c33 = _ip.find_cell_magic('cellm33') - nt.assert_equal(c33, None) + assert c33 == None def test_file(): """Basic %%writefile""" @@ -856,8 +867,9 @@ def test_file(): 'line2', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line2', s) + assert "line1\n" in s + assert "line2" in s + @dec.skip_win32 def test_file_single_quote(): @@ -870,8 +882,9 @@ def test_file_single_quote(): 'line2', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line2', s) + assert "line1\n" in s + assert "line2" in s + @dec.skip_win32 def test_file_double_quote(): @@ -884,8 +897,9 @@ def test_file_double_quote(): 'line2', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line2', s) + assert "line1\n" in s + assert "line2" in s + def test_file_var_expand(): """%%writefile $filename""" @@ -898,8 +912,9 @@ def test_file_var_expand(): 'line2', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line2', s) + assert "line1\n" in s + assert "line2" in s + def test_file_unicode(): """%%writefile with unicode cell""" @@ -912,8 +927,9 @@ def test_file_unicode(): ])) with io.open(fname, encoding='utf-8') as f: s = f.read() - nt.assert_in(u'liné1\n', s) - nt.assert_in(u'liné2', s) + assert "liné1\n" in s + assert "liné2" in s + def test_file_amend(): """%%writefile -a amends files""" @@ -929,8 +945,9 @@ def test_file_amend(): 'line4', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line3\n', s) + assert "line1\n" in s + assert "line3\n" in s + def test_file_spaces(): """%%file with spaces in filename""" @@ -942,14 +959,16 @@ def test_file_spaces(): 'line2', ])) s = Path(fname).read_text() - nt.assert_in('line1\n', s) - nt.assert_in('line2', s) - + assert "line1\n" in s + assert "line2" in s + + def test_script_config(): ip = get_ipython() ip.config.ScriptMagics.script_magics = ['whoda'] sm = script.ScriptMagics(shell=ip) - nt.assert_in('whoda', sm.magics['cell']) + assert "whoda" in sm.magics["cell"] + @dec.skip_iptest_but_not_pytest @dec.skip_win32 @@ -962,7 +981,8 @@ def test_script_out(): ip = get_ipython() ip.run_cell_magic("script", "--out output sh", "echo 'hi'") assert asyncio.get_event_loop().is_running() is False - nt.assert_equal(ip.user_ns['output'], 'hi\n') + assert ip.user_ns["output"] == "hi\n" + @dec.skip_iptest_but_not_pytest @dec.skip_win32 @@ -974,7 +994,7 @@ def test_script_err(): assert asyncio.get_event_loop().is_running() is False ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") assert asyncio.get_event_loop().is_running() is False - nt.assert_equal(ip.user_ns['error'], 'hello\n') + assert ip.user_ns["error"] == "hello\n" @dec.skip_iptest_but_not_pytest @@ -988,8 +1008,8 @@ def test_script_out_err(): ip.run_cell_magic( "script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2" ) - nt.assert_equal(ip.user_ns["output"], "hi\n") - nt.assert_equal(ip.user_ns["error"], "hello\n") + assert ip.user_ns["output"] == "hi\n" + assert ip.user_ns["error"] == "hello\n" @dec.skip_iptest_but_not_pytest @@ -1000,10 +1020,11 @@ def test_script_out_err(): async def test_script_bg_out(): ip = get_ipython() ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") - nt.assert_equal((await ip.user_ns["output"].read()), b"hi\n") + assert (await ip.user_ns["output"].read()) == b"hi\n" ip.user_ns["output"].close() asyncio.get_event_loop().stop() + @dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( @@ -1012,7 +1033,7 @@ async def test_script_bg_out(): async def test_script_bg_err(): ip = get_ipython() ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") - nt.assert_equal((await ip.user_ns["error"].read()), b"hello\n") + assert (await ip.user_ns["error"].read()) == b"hello\n" ip.user_ns["error"].close() @@ -1026,8 +1047,8 @@ async def test_script_bg_out_err(): ip.run_cell_magic( "script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2" ) - nt.assert_equal((await ip.user_ns["output"].read()), b"hi\n") - nt.assert_equal((await ip.user_ns["error"].read()), b"hello\n") + assert (await ip.user_ns["output"].read()) == b"hi\n" + assert (await ip.user_ns["error"].read()) == b"hello\n" ip.user_ns["output"].close() ip.user_ns["error"].close() @@ -1040,7 +1061,7 @@ def test_script_defaults(): except Exception: pass else: - nt.assert_in(cmd, ip.magics_manager.magics['cell']) + assert cmd in ip.magics_manager.magics["cell"] @magics_class @@ -1060,19 +1081,20 @@ def test_line_cell_info(): """%%foo and %foo magics are distinguishable to inspect""" ip = get_ipython() ip.magics_manager.register(FooFoo) - oinfo = ip.object_inspect('foo') - nt.assert_true(oinfo['found']) - nt.assert_true(oinfo['ismagic']) - - oinfo = ip.object_inspect('%%foo') - nt.assert_true(oinfo['found']) - nt.assert_true(oinfo['ismagic']) - nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__) - - oinfo = ip.object_inspect('%foo') - nt.assert_true(oinfo['found']) - nt.assert_true(oinfo['ismagic']) - nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__) + oinfo = ip.object_inspect("foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + + oinfo = ip.object_inspect("%%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ + + oinfo = ip.object_inspect("%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.line_foo.__doc__ + def test_multiple_magics(): ip = get_ipython() @@ -1080,9 +1102,10 @@ def test_multiple_magics(): foo2 = FooFoo(ip) mm = ip.magics_manager mm.register(foo1) - nt.assert_true(mm.magics['line']['foo'].__self__ is foo1) + assert mm.magics["line"]["foo"].__self__ is foo1 mm.register(foo2) - nt.assert_true(mm.magics['line']['foo'].__self__ is foo2) + assert mm.magics["line"]["foo"].__self__ is foo2 + def test_alias_magic(): """Test %alias_magic.""" @@ -1090,48 +1113,49 @@ def test_alias_magic(): mm = ip.magics_manager # Basic operation: both cell and line magics are created, if possible. - ip.run_line_magic('alias_magic', 'timeit_alias timeit') - nt.assert_in('timeit_alias', mm.magics['line']) - nt.assert_in('timeit_alias', mm.magics['cell']) + ip.run_line_magic("alias_magic", "timeit_alias timeit") + assert "timeit_alias" in mm.magics["line"] + assert "timeit_alias" in mm.magics["cell"] # --cell is specified, line magic not created. - ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit') - nt.assert_not_in('timeit_cell_alias', mm.magics['line']) - nt.assert_in('timeit_cell_alias', mm.magics['cell']) + ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") + assert "timeit_cell_alias" not in mm.magics["line"] + assert "timeit_cell_alias" in mm.magics["cell"] # Test that line alias is created successfully. - ip.run_line_magic('alias_magic', '--line env_alias env') - nt.assert_equal(ip.run_line_magic('env', ''), - ip.run_line_magic('env_alias', '')) + ip.run_line_magic("alias_magic", "--line env_alias env") + assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") # Test that line alias with parameters passed in is created successfully. - ip.run_line_magic('alias_magic', '--line history_alias history --params ' + shlex.quote('3')) - nt.assert_in('history_alias', mm.magics['line']) + ip.run_line_magic( + "alias_magic", "--line history_alias history --params " + shlex.quote("3") + ) + assert "history_alias" in mm.magics["line"] def test_save(): """Test %save.""" ip = get_ipython() ip.history_manager.reset() # Clear any existing history. - cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] + cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] for i, cmd in enumerate(cmds, start=1): ip.history_manager.store_inputs(i, cmd) with TemporaryDirectory() as tmpdir: file = os.path.join(tmpdir, "testsave.py") ip.run_line_magic("save", "%s 1-10" % file) content = Path(file).read_text() - nt.assert_equal(content.count(cmds[0]), 1) - nt.assert_in("coding: utf-8", content) + assert content.count(cmds[0]) == 1 + assert "coding: utf-8" in content ip.run_line_magic("save", "-a %s 1-10" % file) content = Path(file).read_text() - nt.assert_equal(content.count(cmds[0]), 2) - nt.assert_in("coding: utf-8", content) + assert content.count(cmds[0]) == 2 + assert "coding: utf-8" in content def test_save_with_no_args(): ip = get_ipython() ip.history_manager.reset() # Clear any existing history. - cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())", "%save"] + cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] for i, cmd in enumerate(cmds, start=1): ip.history_manager.store_inputs(i, cmd) @@ -1148,26 +1172,26 @@ def b(): print(a, b()) """ ) - nt.assert_equal(content, expected_content) + assert content == expected_content def test_store(): """Test %store.""" ip = get_ipython() ip.run_line_magic('load_ext', 'storemagic') - + # make sure the storage is empty - ip.run_line_magic('store', '-z') - ip.user_ns['var'] = 42 - ip.run_line_magic('store', 'var') - ip.user_ns['var'] = 39 - ip.run_line_magic('store', '-r') - nt.assert_equal(ip.user_ns['var'], 42) + ip.run_line_magic("store", "-z") + ip.user_ns["var"] = 42 + ip.run_line_magic("store", "var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 42 - ip.run_line_magic('store', '-d var') - ip.user_ns['var'] = 39 - ip.run_line_magic('store' , '-r') - nt.assert_equal(ip.user_ns['var'], 39) + ip.run_line_magic("store", "-d var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 39 def _run_edit_test(arg_s, exp_filename=None, @@ -1179,29 +1203,29 @@ def _run_edit_test(arg_s, exp_filename=None, last_call = ['',''] opts,args = M.parse_options(arg_s,'prxn:') filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) - + if exp_filename is not None: - nt.assert_equal(exp_filename, filename) + assert exp_filename == filename if exp_contents is not None: with io.open(filename, 'r', encoding='utf-8') as f: contents = f.read() - nt.assert_equal(exp_contents, contents) + assert exp_contents == contents if exp_lineno != -1: - nt.assert_equal(exp_lineno, lineno) + assert exp_lineno == lineno if exp_is_temp is not None: - nt.assert_equal(exp_is_temp, is_temp) + assert exp_is_temp == is_temp def test_edit_interactive(): """%edit on interactively defined objects""" ip = get_ipython() n = ip.execution_count - ip.run_cell(u"def foo(): return 1", store_history=True) - + ip.run_cell("def foo(): return 1", store_history=True) + try: _run_edit_test("foo") except code.InteractivelyDefined as e: - nt.assert_equal(e.index, n) + assert e.index == n else: raise AssertionError("Should have raised InteractivelyDefined") @@ -1209,9 +1233,9 @@ def test_edit_interactive(): def test_edit_cell(): """%edit [cell id]""" ip = get_ipython() - - ip.run_cell(u"def foo(): return 1", store_history=True) - + + ip.run_cell("def foo(): return 1", store_history=True) + # test _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) @@ -1234,17 +1258,18 @@ def test_ls_magic(): lsmagic = ip.magic('lsmagic') with warnings.catch_warnings(record=True) as w: j = json_formatter(lsmagic) - nt.assert_equal(sorted(j), ['cell', 'line']) - nt.assert_equal(w, []) # no warnings + assert sorted(j) == ["cell", "line"] + assert w == [] # no warnings + def test_strip_initial_indent(): def sii(s): lines = s.splitlines() return '\n'.join(code.strip_initial_indent(lines)) - nt.assert_equal(sii(" a = 1\nb = 2"), "a = 1\nb = 2") - nt.assert_equal(sii(" a\n b\nc"), "a\n b\nc") - nt.assert_equal(sii("a\n b"), "a\n b") + assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" + assert sii(" a\n b\nc") == "a\n b\nc" + assert sii("a\n b") == "a\n b" def test_logging_magic_quiet_from_arg(): _ip.config.LoggingMagics.quiet = False @@ -1336,6 +1361,6 @@ def is_package(self, __): _ip.run_cell("import my_tmp") output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" - nt.assert_equal(output, captured.stdout) + assert output == captured.stdout sys.meta_path.pop(0) From b5182174bac2e626424a8a7a6a5440a2683ae739 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:43:44 +0200 Subject: [PATCH 1599/3726] [core][tests][magic_terminal] Remove nose --- IPython/core/tests/test_magic_terminal.py | 67 ++++++++++++----------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/IPython/core/tests/test_magic_terminal.py b/IPython/core/tests/test_magic_terminal.py index 79e2d3ed4a5..3b53aadf068 100644 --- a/IPython/core/tests/test_magic_terminal.py +++ b/IPython/core/tests/test_magic_terminal.py @@ -11,8 +11,6 @@ from io import StringIO from unittest import TestCase -import nose.tools as nt - from IPython.testing import tools as tt #----------------------------------------------------------------------------- @@ -93,16 +91,16 @@ def tearDown(self): ip.hooks.clipboard_get = self.original_clip def test_paste(self): - ip.user_ns.pop('x', None) - self.paste('x = 1') - nt.assert_equal(ip.user_ns['x'], 1) - ip.user_ns.pop('x') + ip.user_ns.pop("x", None) + self.paste("x = 1") + self.assertEqual(ip.user_ns["x"], 1) + ip.user_ns.pop("x") def test_paste_pyprompt(self): - ip.user_ns.pop('x', None) - self.paste('>>> x=2') - nt.assert_equal(ip.user_ns['x'], 2) - ip.user_ns.pop('x') + ip.user_ns.pop("x", None) + self.paste(">>> x=2") + self.assertEqual(ip.user_ns["x"], 2) + ip.user_ns.pop("x") def test_paste_py_multi(self): self.paste(""" @@ -111,35 +109,38 @@ def test_paste_py_multi(self): >>> for i in x: ... y.append(i**2) ... - """) - nt.assert_equal(ip.user_ns['x'], [1,2,3]) - nt.assert_equal(ip.user_ns['y'], [1,4,9]) + """ + ) + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) def test_paste_py_multi_r(self): "Now, test that self.paste -r works" self.test_paste_py_multi() - nt.assert_equal(ip.user_ns.pop('x'), [1,2,3]) - nt.assert_equal(ip.user_ns.pop('y'), [1,4,9]) - nt.assert_false('x' in ip.user_ns) - ip.magic('paste -r') - nt.assert_equal(ip.user_ns['x'], [1,2,3]) - nt.assert_equal(ip.user_ns['y'], [1,4,9]) + self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3]) + self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9]) + self.assertFalse("x" in ip.user_ns) + ip.magic("paste -r") + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) def test_paste_email(self): "Test pasting of email-quoted contents" self.paste("""\ >> def foo(x): >> return x + 1 - >> xx = foo(1.1)""") - nt.assert_equal(ip.user_ns['xx'], 2.1) + >> xx = foo(1.1)""" + ) + self.assertEqual(ip.user_ns["xx"], 2.1) def test_paste_email2(self): "Email again; some programs add a space also at each quoting level" self.paste("""\ > > def foo(x): > > return x + 1 - > > yy = foo(2.1) """) - nt.assert_equal(ip.user_ns['yy'], 3.1) + > > yy = foo(2.1) """ + ) + self.assertEqual(ip.user_ns["yy"], 3.1) def test_paste_email_py(self): "Email quoting of interactive input" @@ -147,8 +148,9 @@ def test_paste_email_py(self): >> >>> def f(x): >> ... return x+1 >> ... - >> >>> zz = f(2.5) """) - nt.assert_equal(ip.user_ns['zz'], 3.5) + >> >>> zz = f(2.5) """ + ) + self.assertEqual(ip.user_ns["zz"], 3.5) def test_paste_echo(self): "Also test self.paste echoing, by temporarily faking the writer" @@ -163,9 +165,9 @@ def test_paste_echo(self): out = w.getvalue() finally: ip.write = writer - nt.assert_equal(ip.user_ns['a'], 100) - nt.assert_equal(ip.user_ns['b'], 200) - assert out == code+"\n## -- End pasted text --\n" + self.assertEqual(ip.user_ns["a"], 100) + self.assertEqual(ip.user_ns["b"], 200) + assert out == code + "\n## -- End pasted text --\n" def test_paste_leading_commas(self): "Test multiline strings with leading commas" @@ -174,10 +176,9 @@ def test_paste_leading_commas(self): a = """ ,1,2,3 """''' - ip.user_ns.pop('foo', None) - tm.store_or_execute(s, 'foo') - nt.assert_in('foo', ip.user_ns) - + ip.user_ns.pop("foo", None) + tm.store_or_execute(s, "foo") + self.assertIn("foo", ip.user_ns) def test_paste_trailing_question(self): "Test pasting sources with trailing question marks" @@ -189,4 +190,4 @@ def funcfoo(): ''' ip.user_ns.pop('funcfoo', None) self.paste(s) - nt.assert_equal(ip.user_ns['funcfoo'](), 'fooresult') + self.assertEqual(ip.user_ns["funcfoo"](), "fooresult") From 931096b771bc3bf0d92bd7e60a46de13cd76f25c Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:47:49 +0200 Subject: [PATCH 1600/3726] [core][tests][oinspect] Remove nose --- IPython/core/tests/test_oinspect.py | 69 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index 6d3ae2e5c44..7557fcf804b 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -9,8 +9,6 @@ import os import re -import nose.tools as nt - from .. import oinspect from decorator import decorator @@ -38,7 +36,7 @@ def setup_module(): # defined, if any code is inserted above, the following line will need to be # updated. Do NOT insert any whitespace between the next line and the function # definition below. -THIS_LINE_NUMBER = 41 # Put here the actual number of this line +THIS_LINE_NUMBER = 39 # Put here the actual number of this line from unittest import TestCase @@ -74,7 +72,7 @@ def wrapper(*a, **kw): @noop1 def f(x): "My docstring" - + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) assert f.__doc__ == "My docstring" @@ -90,14 +88,14 @@ def noop2(f, *a, **kw): @noop2 def f(x): "My docstring 2" - + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) assert f.__doc__ == "My docstring 2" - + def test_find_file_magic(): run = ip.find_line_magic('run') - nt.assert_not_equal(oinspect.find_file(run), None) + assert oinspect.find_file(run) is not None # A few generic objects we can then inspect in the tests below @@ -169,11 +167,11 @@ def test_info(): "Check that Inspector.info fills out various fields as expected." i = inspector.info(Call, oname="Call") assert i["type_name"] == "type" - expted_class = str(type(type)) # (Python 3) or - assert i["base_class"] == expted_class - nt.assert_regex( - i["string_form"], + expected_class = str(type(type)) # (Python 3) or + assert i["base_class"] == expected_class + assert re.search( "", + i["string_form"], ) fname = __file__ if fname.endswith(".pyc"): @@ -184,12 +182,12 @@ def test_info(): assert i["definition"] == None assert i["docstring"] == Call.__doc__ assert i["source"] == None - nt.assert_true(i["isclass"]) + assert i["isclass"] is True assert i["init_definition"] == "Call(x, y=1)" assert i["init_docstring"] == Call.__init__.__doc__ i = inspector.info(Call, detail_level=1) - nt.assert_not_equal(i["source"], None) + assert i["source"] is not None assert i["docstring"] == None c = Call(1) @@ -221,7 +219,7 @@ def test_info_serialliar(): # Nested attribute access should be cut off at 100 levels deep to avoid # infinite loops: https://github.com/ipython/ipython/issues/9122 - nt.assert_less(fib_tracker[0], 9000) + assert fib_tracker[0] < 9000 def support_function_one(x, y=2, *a, **kw): """A simple function.""" @@ -230,7 +228,8 @@ def test_calldef_none(): # We should ignore __call__ for all of these. for obj in [support_function_one, SimpleClass().method, any, str.upper]: i = inspector.info(obj) - nt.assert_is(i['call_def'], None) + assert i["call_def"] is None + def f_kwarg(pos, *, kwonly): pass @@ -249,16 +248,16 @@ class B(object): """standard docstring""" def getdoc(self): return "custom docstring" - + class C(object): """standard docstring""" def getdoc(self): return None - + a = A() b = B() c = C() - + assert oinspect.getdoc(a) == "standard docstring" assert oinspect.getdoc(b) == "custom docstring" assert oinspect.getdoc(c) == "standard docstring" @@ -266,17 +265,17 @@ def getdoc(self): def test_empty_property_has_no_source(): i = inspector.info(property(), detail_level=1) - nt.assert_is(i['source'], None) + assert i["source"] is None def test_property_sources(): - import posixpath + import posixpath # A simple adder whose source and signature stays # the same across Python distributions def simple_add(a, b): "Adds two numbers" return a + b - + class A(object): @property def foo(self): @@ -285,17 +284,17 @@ def foo(self): foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) dname = property(posixpath.dirname) - adder = property(simple_add) + adder = property(simple_add) i = inspector.info(A.foo, detail_level=1) - nt.assert_in('def foo(self):', i['source']) - nt.assert_in('lambda self, v:', i['source']) + assert "def foo(self):" in i["source"] + assert "lambda self, v:" in i["source"] i = inspector.info(A.dname, detail_level=1) - nt.assert_in('def dirname(p)', i['source']) - + assert "def dirname(p)" in i["source"] + i = inspector.info(A.adder, detail_level=1) - nt.assert_in('def simple_add(a, b)', i['source']) + assert "def simple_add(a, b)" in i["source"] def test_property_docstring_is_in_info_for_detail_level_0(): @@ -367,11 +366,11 @@ def test_pinfo_docstring_if_detail_and_no_source(): def bar(self): """ This is a docstring for Foo.bar """ pass - ''' - + ''' + ip.run_cell(obj_def) ip.run_cell('foo = Foo()') - + with AssertNotPrints("Source:"): with AssertPrints('Docstring:'): ip._inspect('pinfo', 'foo', detail_level=0) @@ -396,14 +395,14 @@ def test_pinfo_magic(): def test_init_colors(): # ensure colors are not present in signature info info = inspector.info(HasSignature) - init_def = info['init_definition'] - nt.assert_not_in('[0m', init_def) + init_def = info["init_definition"] + assert "[0m" not in init_def def test_builtin_init(): info = inspector.info(list) init_def = info['init_definition'] - nt.assert_is_not_none(init_def) + assert init_def is not None def test_render_signature_short(): @@ -428,7 +427,7 @@ def long_function( signature(long_function), long_function.__name__, ) - nt.assert_in(sig, [ + assert sig in [ # Python >=3.9 '''\ long_function( @@ -452,4 +451,4 @@ def long_function( let_us_make_sure_this_is_looong:Union[str, NoneType]=None, ) -> bool\ ''', - ]) + ] From a524414a9e838c235623b02303ecb8f00ecaf926 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:48:53 +0200 Subject: [PATCH 1601/3726] [core][tests][paths] Remove nose --- IPython/core/tests/test_paths.py | 53 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/IPython/core/tests/test_paths.py b/IPython/core/tests/test_paths.py index 2182cb7cb0c..e5de945e944 100644 --- a/IPython/core/tests/test_paths.py +++ b/IPython/core/tests/test_paths.py @@ -6,7 +6,6 @@ import warnings from unittest.mock import patch -import nose.tools as nt from testpath import modified_env, assert_isdir, assert_isfile from IPython import paths @@ -52,7 +51,7 @@ def test_get_ipython_dir_1(): modified_env({'IPYTHONDIR': env_ipdir}): ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, env_ipdir) + assert ipdir == env_ipdir def test_get_ipython_dir_2(): """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" @@ -66,7 +65,7 @@ def test_get_ipython_dir_2(): }): ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, os.path.join("someplace", ".ipython")) + assert ipdir == os.path.join("someplace", ".ipython") def test_get_ipython_dir_3(): """test_get_ipython_dir_3, move XDG if defined, and .ipython doesn't exist.""" @@ -81,10 +80,10 @@ def test_get_ipython_dir_3(): }), warnings.catch_warnings(record=True) as w: ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, os.path.join(tmphome.name, ".ipython")) + assert ipdir == os.path.join(tmphome.name, ".ipython") if sys.platform != 'darwin': - nt.assert_equal(len(w), 1) - nt.assert_in('Moving', str(w[0])) + assert len(w) == 1 + assert "Moving" in str(w[0]) finally: tmphome.cleanup() @@ -106,10 +105,11 @@ def test_get_ipython_dir_4(): }), warnings.catch_warnings(record=True) as w: ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, os.path.join(HOME_TEST_DIR, ".ipython")) + assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython") if sys.platform != 'darwin': - nt.assert_equal(len(w), 1) - nt.assert_in('Ignoring', str(w[0])) + assert len(w) == 1 + assert "Ignoring" in str(w[0]) + def test_get_ipython_dir_5(): """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" @@ -128,7 +128,7 @@ def test_get_ipython_dir_5(): }): ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, IP_TEST_DIR) + assert ipdir == IP_TEST_DIR def test_get_ipython_dir_6(): """test_get_ipython_dir_6, use home over XDG if defined and neither exist.""" @@ -146,8 +146,8 @@ def test_get_ipython_dir_6(): }), warnings.catch_warnings(record=True) as w: ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, os.path.join(HOME_TEST_DIR, '.ipython')) - nt.assert_equal(len(w), 0) + assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython") + assert len(w) == 0 def test_get_ipython_dir_7(): """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" @@ -155,7 +155,8 @@ def test_get_ipython_dir_7(): with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \ patch.object(paths, '_writable_dir', return_value=True): ipdir = paths.get_ipython_dir() - nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere')) + assert ipdir == os.path.join(home_dir, "somewhere") + @skip_win32 def test_get_ipython_dir_8(): @@ -164,14 +165,16 @@ def test_get_ipython_dir_8(): # test only when HOME directory actually writable return - with patch.object(paths, '_writable_dir', lambda path: bool(path)), \ - patch.object(paths, 'get_xdg_dir', return_value=None), \ - modified_env({ - 'IPYTHON_DIR': None, - 'IPYTHONDIR': None, - 'HOME': '/', - }): - nt.assert_equal(paths.get_ipython_dir(), '/.ipython') + with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object( + paths, "get_xdg_dir", return_value=None + ), modified_env( + { + "IPYTHON_DIR": None, + "IPYTHONDIR": None, + "HOME": "/", + } + ): + assert paths.get_ipython_dir() == "/.ipython" def test_get_ipython_cache_dir(): @@ -181,18 +184,16 @@ def test_get_ipython_cache_dir(): os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) with modified_env({'XDG_CACHE_HOME': None}): ipdir = paths.get_ipython_cache_dir() - nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"), - ipdir) + assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir assert_isdir(ipdir) # test env override with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}): ipdir = paths.get_ipython_cache_dir() assert_isdir(ipdir) - nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython")) + assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython") else: - nt.assert_equal(paths.get_ipython_cache_dir(), - paths.get_ipython_dir()) + assert paths.get_ipython_cache_dir() == paths.get_ipython_dir() def test_get_ipython_package_dir(): ipdir = paths.get_ipython_package_dir() From eb08dcfc3f0caefd722688b9734b9d67f61cc458 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 11:50:29 +0200 Subject: [PATCH 1602/3726] [core][tests][profile] Remove nose --- IPython/core/tests/test_profile.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/IPython/core/tests/test_profile.py b/IPython/core/tests/test_profile.py index f18a064154a..07f43e4ec05 100644 --- a/IPython/core/tests/test_profile.py +++ b/IPython/core/tests/test_profile.py @@ -27,8 +27,6 @@ from pathlib import Path from unittest import TestCase -import nose.tools as nt - from IPython.core.profileapp import list_profiles_in, list_bundled_profiles from IPython.core.profiledir import ProfileDir @@ -79,7 +77,7 @@ def win32_without_pywin32(): except ImportError: return True return False - + class ProfileStartupTest(TestCase): def setUp(self): @@ -114,7 +112,7 @@ def test_startup_ipy(self): self.init('00-start.ipy', '%xmode plain\n', '') self.validate('Exception reporting mode: Plain') - + def test_list_profiles_in(): # No need to remove these directories and files, as they will get nuked in # the module-level teardown. @@ -127,7 +125,7 @@ def test_list_profiles_in(): with open(td / "profile_file", "w") as f: f.write("I am not a profile directory") profiles = list_profiles_in(td) - + # unicode normalization can turn u'ünicode' into u'u\0308nicode', # so only check for *nicode, and that creating a ProfileDir from the # name remains valid @@ -139,14 +137,14 @@ def test_list_profiles_in(): found_unicode = True break if dec.unicode_paths: - nt.assert_true(found_unicode) - nt.assert_equal(set(profiles), {'foo', 'hello'}) + assert found_unicode is True + assert set(profiles) == {"foo", "hello"} def test_list_bundled_profiles(): # This variable will need to be updated when a new profile gets bundled bundled = sorted(list_bundled_profiles()) - nt.assert_equal(bundled, []) + assert bundled == [] def test_profile_create_ipython_dir(): @@ -167,4 +165,3 @@ def test_profile_create_ipython_dir(): assert Path(profile_dir).exists() ipython_config = profile_dir / "ipython_config.py" assert Path(ipython_config).exists() - From c2c26d9f9ad819b55092c419679270be0dc34e1d Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:49:11 +0200 Subject: [PATCH 1603/3726] [core][tests][pylabtools] Remove nose --- IPython/core/tests/test_pylabtools.py | 117 +++++++++++++------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 426930d54fa..64acd3d465a 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -12,12 +12,10 @@ matplotlib.use('Agg') from matplotlib.figure import Figure -from nose import SkipTest -import nose.tools as nt - from matplotlib import pyplot as plt -import matplotlib_inline +from matplotlib_inline import backend_inline import numpy as np +import pytest from IPython.core.getipython import get_ipython from IPython.core.interactiveshell import InteractiveShell @@ -30,7 +28,7 @@ def test_figure_to_svg(): # simple empty-figure test fig = plt.figure() - nt.assert_equal(pt.print_figure(fig, 'svg'), None) + assert pt.print_figure(fig, "svg") is None plt.close('all') @@ -39,8 +37,9 @@ def test_figure_to_svg(): ax = fig.add_subplot(1,1,1) ax.plot([1,2,3]) plt.draw() - svg = pt.print_figure(fig, 'svg')[:100].lower() - nt.assert_in(u'doctype svg', svg) + svg = pt.print_figure(fig, "svg")[:100].lower() + assert "doctype svg" in svg + def _check_pil_jpeg_bytes(): """Skip if PIL can't write JPEGs to BytesIO objects""" @@ -53,7 +52,7 @@ def _check_pil_jpeg_bytes(): img.save(buf, 'jpeg') except Exception as e: ename = e.__class__.__name__ - raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e + raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e @dec.skip_without("PIL.Image") def test_figure_to_jpeg(): @@ -69,7 +68,7 @@ def test_figure_to_jpeg(): def test_retina_figure(): # simple empty-figure test fig = plt.figure() - nt.assert_equal(pt.retina_figure(fig), None) + assert pt.retina_figure(fig) == None plt.close('all') fig = plt.figure() @@ -78,8 +77,9 @@ def test_retina_figure(): plt.draw() png, md = pt.retina_figure(fig) assert png.startswith(_PNG) - nt.assert_in('width', md) - nt.assert_in('height', md) + assert "width" in md + assert "height" in md + _fmt_mime_map = { 'png': 'image/png', @@ -95,9 +95,9 @@ def test_select_figure_formats_str(): pt.select_figure_formats(ip, fmt) for mime, f in ip.display_formatter.formatters.items(): if mime == active_mime: - nt.assert_in(Figure, f) + assert Figure in f else: - nt.assert_not_in(Figure, f) + assert Figure not in f def test_select_figure_formats_kwargs(): ip = get_ipython() @@ -134,24 +134,25 @@ def test_select_figure_formats_set(): pt.select_figure_formats(ip, fmts) for mime, f in ip.display_formatter.formatters.items(): if mime in active_mimes: - nt.assert_in(Figure, f) + assert Figure in f else: - nt.assert_not_in(Figure, f) + assert Figure not in f def test_select_figure_formats_bad(): ip = get_ipython() - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): pt.select_figure_formats(ip, 'foo') - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): pt.select_figure_formats(ip, {'png', 'foo'}) - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad']) def test_import_pylab(): ns = {} pt.import_pylab(ns, import_all=False) - nt.assert_true('plt' in ns) - nt.assert_equal(ns['np'], np) + assert "plt" in ns + assert ns["np"] == np + from traitlets.config import Config @@ -182,15 +183,13 @@ def act_mpl(backend): pt.activate_matplotlib = act_mpl self._save_ip = pt.import_pylab pt.import_pylab = lambda *a,**kw:None - self._save_cis = matplotlib_inline.backend_inline.configure_inline_support - matplotlib_inline.backend_inline.configure_inline_support = ( - lambda *a, **kw: None - ) + self._save_cis = backend_inline.configure_inline_support + backend_inline.configure_inline_support = lambda *a, **kw: None def teardown(self): pt.activate_matplotlib = self._save_am pt.import_pylab = self._save_ip - matplotlib_inline.backend_inline.configure_inline_support = self._save_cis + backend_inline.configure_inline_support = self._save_cis import matplotlib matplotlib.rcParams = self._saved_rcParams matplotlib.rcParamsOrig = self._saved_rcParamsOrig @@ -199,68 +198,68 @@ def test_qt(self): s = self.Shell() gui, backend = s.enable_matplotlib(None) - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + assert gui == "qt" + assert s.pylab_gui_select == "qt" - gui, backend = s.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == "qt" - gui, backend = s.enable_matplotlib('qt') - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" - gui, backend = s.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == "qt" gui, backend = s.enable_matplotlib() - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + assert gui == "qt" + assert s.pylab_gui_select == "qt" def test_inline(self): s = self.Shell() - gui, backend = s.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') - nt.assert_equal(s.pylab_gui_select, None) + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == None - gui, backend = s.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') - nt.assert_equal(s.pylab_gui_select, None) + gui, backend = s.enable_matplotlib("inline") + assert gui == "inline" + assert s.pylab_gui_select == None - gui, backend = s.enable_matplotlib('qt') - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" def test_inline_twice(self): "Using '%matplotlib inline' twice should not reset formatters" ip = self.Shell() - gui, backend = ip.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') + gui, backend = ip.enable_matplotlib("inline") + assert gui == "inline" fmts = {'png'} active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} pt.select_figure_formats(ip, fmts) - gui, backend = ip.enable_matplotlib('inline') - nt.assert_equal(gui, 'inline') + gui, backend = ip.enable_matplotlib("inline") + assert gui == "inline" for mime, f in ip.display_formatter.formatters.items(): if mime in active_mimes: - nt.assert_in(Figure, f) + assert Figure in f else: - nt.assert_not_in(Figure, f) + assert Figure not in f def test_qt_gtk(self): s = self.Shell() - gui, backend = s.enable_matplotlib('qt') - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" - gui, backend = s.enable_matplotlib('gtk') - nt.assert_equal(gui, 'qt') - nt.assert_equal(s.pylab_gui_select, 'qt') + gui, backend = s.enable_matplotlib("gtk") + assert gui == "qt" + assert s.pylab_gui_select == "qt" def test_no_gui_backends(): From 421d89b74f259a03391a41d03f640c5d45aefad3 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 12:01:00 +0200 Subject: [PATCH 1604/3726] [core][tests][run] Remove nose --- IPython/core/tests/test_run.py | 168 ++++++++++++++++----------------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 5b690645af9..c593a82da6a 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -26,8 +26,7 @@ import unittest from unittest.mock import patch -import nose.tools as nt -from nose import SkipTest +import pytest from IPython.testing import decorators as dec from IPython.testing import tools as tt @@ -168,13 +167,13 @@ class TestMagicRunPass(tt.TempFileMixin): def setUp(self): content = "a = [1,2,3]\nb = 1" self.mktmp(content) - + def run_tmpfile(self): _ip = get_ipython() # This fails on Windows if self.tmpfile.name has spaces or "~" in it. # See below and ticket https://bugs.launchpad.net/bugs/366353 _ip.magic('run %s' % self.fname) - + def run_tmpfile_p(self): _ip = get_ipython() # This fails on Windows if self.tmpfile.name has spaces or "~" in it. @@ -188,7 +187,7 @@ def test_builtins_id(self): bid1 = id(_ip.user_ns['__builtins__']) self.run_tmpfile() bid2 = id(_ip.user_ns['__builtins__']) - nt.assert_equal(bid1, bid2) + assert bid1 == bid2 def test_builtins_type(self): """Check that the type of __builtins__ doesn't change with %run. @@ -199,9 +198,9 @@ def test_builtins_type(self): """ _ip = get_ipython() self.run_tmpfile() - nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys)) - - def test_run_profile( self ): + assert type(_ip.user_ns["__builtins__"]) == type(sys) + + def test_run_profile(self): """Test that the option -p, which invokes the profiler, do not crash by invoking execfile""" self.run_tmpfile_p() @@ -232,9 +231,9 @@ def test_simpledef(self): src = ("class foo: pass\n" "def f(): return foo()") self.mktmp(src) - _ip.magic('run %s' % self.fname) - _ip.run_cell('t = isinstance(f(), foo)') - nt.assert_true(_ip.user_ns['t']) + _ip.magic("run %s" % self.fname) + _ip.run_cell("t = isinstance(f(), foo)") + assert _ip.user_ns["t"] is True def test_obj_del(self): """Test that object's __del__ methods are called on exit.""" @@ -242,7 +241,7 @@ def test_obj_del(self): try: import win32api except ImportError as e: - raise SkipTest("Test requires pywin32") from e + raise unittest.SkipTest("Test requires pywin32") from e src = ("class A(object):\n" " def __del__(self):\n" " print('object A deleted')\n" @@ -250,42 +249,41 @@ def test_obj_del(self): self.mktmp(src) err = None tt.ipexec_validate(self.fname, 'object A deleted', err) - + def test_aggressive_namespace_cleanup(self): """Test that namespace cleanup is not too aggressive GH-238 Returning from another run magic deletes the namespace""" # see ticket https://github.com/ipython/ipython/issues/238 - + with tt.TempFileMixin() as empty: - empty.mktmp('') + empty.mktmp("") # On Windows, the filename will have \users in it, so we need to use the # repr so that the \u becomes \\u. - src = ("ip = get_ipython()\n" - "for i in range(5):\n" - " try:\n" - " ip.magic(%r)\n" - " except NameError as e:\n" - " print(i)\n" - " break\n" % ('run ' + empty.fname)) + src = ( + "ip = get_ipython()\n" + "for i in range(5):\n" + " try:\n" + " ip.magic(%r)\n" + " except NameError as e:\n" + " print(i)\n" + " break\n" % ("run " + empty.fname) + ) self.mktmp(src) - _ip.magic('run %s' % self.fname) - _ip.run_cell('ip == get_ipython()') - nt.assert_equal(_ip.user_ns['i'], 4) - + _ip.magic("run %s" % self.fname) + _ip.run_cell("ip == get_ipython()") + assert _ip.user_ns["i"] == 4 + def test_run_second(self): - """Test that running a second file doesn't clobber the first, gh-3547 - """ - self.mktmp("avar = 1\n" - "def afunc():\n" - " return avar\n") + """Test that running a second file doesn't clobber the first, gh-3547""" + self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n") with tt.TempFileMixin() as empty: empty.mktmp("") - - _ip.magic('run %s' % self.fname) - _ip.magic('run %s' % empty.fname) - nt.assert_equal(_ip.user_ns['afunc'](), 1) + + _ip.magic("run %s" % self.fname) + _ip.magic("run %s" % empty.fname) + assert _ip.user_ns["afunc"]() == 1 @dec.skip_win32 def test_tclass(self): @@ -312,24 +310,24 @@ def test_run_i_after_reset(self): self.mktmp(src) _ip.run_cell("zz = 23") try: - _ip.magic('run -i %s' % self.fname) - nt.assert_equal(_ip.user_ns['yy'], 23) + _ip.magic("run -i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 finally: _ip.magic('reset -f') - + _ip.run_cell("zz = 23") try: - _ip.magic('run -i %s' % self.fname) - nt.assert_equal(_ip.user_ns['yy'], 23) + _ip.magic("run -i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 finally: _ip.magic('reset -f') - + def test_unicode(self): """Check that files in odd encodings are accepted.""" mydir = os.path.dirname(__file__) na = os.path.join(mydir, 'nonascii.py') _ip.magic('run "%s"' % na) - nt.assert_equal(_ip.user_ns['u'], u'Ўт№Ф') + assert _ip.user_ns["u"] == "Ўт№Ф" def test_run_py_file_attribute(self): """Test handling of `__file__` attribute in `%run .py`.""" @@ -342,10 +340,10 @@ def test_run_py_file_attribute(self): # Check that __file__ was equal to the filename in the script's # namespace. - nt.assert_equal(_ip.user_ns['t'], self.fname) + assert _ip.user_ns["t"] == self.fname # Check that __file__ was not leaked back into user_ns. - nt.assert_equal(file1, file2) + assert file1 == file2 def test_run_ipy_file_attribute(self): """Test handling of `__file__` attribute in `%run `.""" @@ -358,10 +356,10 @@ def test_run_ipy_file_attribute(self): # Check that __file__ was equal to the filename in the script's # namespace. - nt.assert_equal(_ip.user_ns['t'], self.fname) + assert _ip.user_ns["t"] == self.fname # Check that __file__ was not leaked back into user_ns. - nt.assert_equal(file1, file2) + assert file1 == file2 def test_run_formatting(self): """ Test that %run -t -N does not raise a TypeError for N > 1.""" @@ -369,14 +367,14 @@ def test_run_formatting(self): self.mktmp(src) _ip.magic('run -t -N 1 %s' % self.fname) _ip.magic('run -t -N 10 %s' % self.fname) - + def test_ignore_sys_exit(self): """Test the -e option to ignore sys.exit()""" src = "import sys; sys.exit(1)" self.mktmp(src) with tt.AssertPrints('SystemExit'): _ip.magic('run %s' % self.fname) - + with tt.AssertNotPrints('SystemExit'): _ip.magic('run -e %s' % self.fname) @@ -391,19 +389,19 @@ def test_run_nb(self): ) src = writes(nb, version=4) self.mktmp(src, ext='.ipynb') - + _ip.magic("run %s" % self.fname) - - nt.assert_equal(_ip.user_ns['answer'], 42) + + assert _ip.user_ns["answer"] == 42 def test_run_nb_error(self): """Test %run notebook.ipynb error""" from nbformat import v4, writes # %run when a file name isn't provided - nt.assert_raises(Exception, _ip.magic, "run") + pytest.raises(Exception, _ip.magic, "run") # %run when a file doesn't exist - nt.assert_raises(Exception, _ip.magic, "run foobar.ipynb") + pytest.raises(Exception, _ip.magic, "run foobar.ipynb") # %run on a notebook with an error nb = v4.new_notebook( @@ -413,15 +411,15 @@ def test_run_nb_error(self): ) src = writes(nb, version=4) self.mktmp(src, ext='.ipynb') - nt.assert_raises(Exception, _ip.magic, "run %s" % self.fname) + pytest.raises(Exception, _ip.magic, "run %s" % self.fname) def test_file_options(self): src = ('import sys\n' 'a = " ".join(sys.argv[1:])\n') self.mktmp(src) - test_opts = '-x 3 --verbose' - _ip.run_line_magic("run", '{0} {1}'.format(self.fname, test_opts)) - nt.assert_equal(_ip.user_ns['a'], test_opts) + test_opts = "-x 3 --verbose" + _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts)) + assert _ip.user_ns["a"] == test_opts class TestMagicRunWithPackage(unittest.TestCase): @@ -500,33 +498,34 @@ def test_debug_run_submodule_with_relative_import(self): self.check_run_submodule('relative', '-d') def test_module_options(self): - _ip.user_ns.pop('a', None) - test_opts = '-x abc -m test' - _ip.run_line_magic('run', '-m {0}.args {1}'.format(self.package, test_opts)) - nt.assert_equal(_ip.user_ns['a'], test_opts) + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts def test_module_options_with_separator(self): - _ip.user_ns.pop('a', None) - test_opts = '-x abc -m test' - _ip.run_line_magic('run', '-m {0}.args -- {1}'.format(self.package, test_opts)) - nt.assert_equal(_ip.user_ns['a'], test_opts) + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts + def test_run__name__(): with TemporaryDirectory() as td: path = pjoin(td, 'foo.py') with open(path, 'w') as f: f.write("q = __name__") - - _ip.user_ns.pop('q', None) - _ip.magic('run {}'.format(path)) - nt.assert_equal(_ip.user_ns.pop('q'), '__main__') - - _ip.magic('run -n {}'.format(path)) - nt.assert_equal(_ip.user_ns.pop('q'), 'foo') + + _ip.user_ns.pop("q", None) + _ip.magic("run {}".format(path)) + assert _ip.user_ns.pop("q") == "__main__" + + _ip.magic("run -n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" try: - _ip.magic('run -i -n {}'.format(path)) - nt.assert_equal(_ip.user_ns.pop('q'), 'foo') + _ip.magic("run -i -n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" finally: _ip.magic('reset -f') @@ -546,9 +545,9 @@ def test_run_tb(): with capture_output() as io: _ip.magic('run {}'.format(path)) out = io.stdout - nt.assert_not_in("execfile", out) - nt.assert_in("RuntimeError", out) - nt.assert_equal(out.count("---->"), 3) + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 del ip.user_ns['bar'] del ip.user_ns['foo'] @@ -572,10 +571,10 @@ def test_multiprocessing_run(): _ip.run_line_magic('run', path) _ip.run_cell("i_m_undefined") out = io.stdout - nt.assert_in("hoy", out) - nt.assert_not_in("AttributeError", out) - nt.assert_in("NameError", out) - nt.assert_equal(out.count("---->"), 1) + assert "hoy" in out + assert "AttributeError" not in out + assert "NameError" in out + assert out.count("---->") == 1 except: raise finally: @@ -595,7 +594,6 @@ def test_script_tb(): "foo()", ])) out, err = tt.ipexec(path) - nt.assert_not_in("execfile", out) - nt.assert_in("RuntimeError", out) - nt.assert_equal(out.count("---->"), 3) - + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 From 6d0a78f61354592ce546fb41eb5eb80b5dda993b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 12:02:16 +0200 Subject: [PATCH 1605/3726] [core][tests][splitinput] Remove nose --- IPython/core/tests/test_splitinput.py | 52 +++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/IPython/core/tests/test_splitinput.py b/IPython/core/tests/test_splitinput.py index 98b4189e5b4..8969da250ae 100644 --- a/IPython/core/tests/test_splitinput.py +++ b/IPython/core/tests/test_splitinput.py @@ -1,38 +1,38 @@ # coding: utf-8 -import nose.tools as nt from IPython.core.splitinput import split_user_input, LineInfo from IPython.testing import tools as tt tests = [ - ('x=1', ('', '', 'x', '=1')), - ('?', ('', '?', '', '')), - ('??', ('', '??', '', '')), - (' ?', (' ', '?', '', '')), - (' ??', (' ', '??', '', '')), - ('??x', ('', '??', 'x', '')), - ('?x=1', ('', '?', 'x', '=1')), - ('!ls', ('', '!', 'ls', '')), - (' !ls', (' ', '!', 'ls', '')), - ('!!ls', ('', '!!', 'ls', '')), - (' !!ls', (' ', '!!', 'ls', '')), - (',ls', ('', ',', 'ls', '')), - (';ls', ('', ';', 'ls', '')), - (' ;ls', (' ', ';', 'ls', '')), - ('f.g(x)', ('', '', 'f.g', '(x)')), - ('f.g (x)', ('', '', 'f.g', '(x)')), - ('?%hist1', ('', '?', '%hist1', '')), - ('?%%hist2', ('', '?', '%%hist2', '')), - ('??%hist3', ('', '??', '%hist3', '')), - ('??%%hist4', ('', '??', '%%hist4', '')), - ('?x*', ('', '?', 'x*', '')), - ] -tests.append((u"Pérez Fernando", (u'', u'', u'Pérez', u'Fernando'))) + ("x=1", ("", "", "x", "=1")), + ("?", ("", "?", "", "")), + ("??", ("", "??", "", "")), + (" ?", (" ", "?", "", "")), + (" ??", (" ", "??", "", "")), + ("??x", ("", "??", "x", "")), + ("?x=1", ("", "?", "x", "=1")), + ("!ls", ("", "!", "ls", "")), + (" !ls", (" ", "!", "ls", "")), + ("!!ls", ("", "!!", "ls", "")), + (" !!ls", (" ", "!!", "ls", "")), + (",ls", ("", ",", "ls", "")), + (";ls", ("", ";", "ls", "")), + (" ;ls", (" ", ";", "ls", "")), + ("f.g(x)", ("", "", "f.g", "(x)")), + ("f.g (x)", ("", "", "f.g", "(x)")), + ("?%hist1", ("", "?", "%hist1", "")), + ("?%%hist2", ("", "?", "%%hist2", "")), + ("??%hist3", ("", "??", "%hist3", "")), + ("??%%hist4", ("", "??", "%%hist4", "")), + ("?x*", ("", "?", "x*", "")), +] +tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando"))) + def test_split_user_input(): return tt.check_pairs(split_user_input, tests) def test_LineInfo(): """Simple test for LineInfo construction and str()""" - linfo = LineInfo(' %cd /home') - nt.assert_equal(str(linfo), 'LineInfo [ |%|cd|/home]') + linfo = LineInfo(" %cd /home") + assert str(linfo) == "LineInfo [ |%|cd|/home]" From 12d08a51494137e81dc0cd631eef5afdc46ae07f Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 12:03:00 +0200 Subject: [PATCH 1606/3726] [extensions][tests][autoreload] Remove nose --- IPython/extensions/tests/test_autoreload.py | 75 ++++++++++----------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index 4bccd4068e0..e03a08eec97 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -21,7 +21,6 @@ import time from io import StringIO -import nose.tools as nt import IPython.testing.tools as tt from unittest import TestCase @@ -227,12 +226,12 @@ def square(self): self.shell.run_code("from %s import MyClass" % mod_name) self.shell.run_code("first = MyClass(5)") self.shell.run_code("first.square()") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code("first.cube()") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code("first.power(5)") self.shell.run_code("first.b") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code("first.toto") # remove square, add power @@ -258,13 +257,13 @@ def power(self, p): for object_name in {"first", "second"}: self.shell.run_code(f"{object_name}.power(5)") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code(f"{object_name}.cube()") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code(f"{object_name}.square()") self.shell.run_code(f"{object_name}.b") self.shell.run_code(f"{object_name}.a") - with nt.assert_raises(AttributeError): + with self.assertRaises(AttributeError): self.shell.run_code(f"{object_name}.toto") def test_autoload_newly_added_objects(self): @@ -275,11 +274,11 @@ def func1(): pass mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code)) self.shell.run_code(f"from {mod_name} import *") self.shell.run_code("func1()") - with nt.assert_raises(NameError): + with self.assertRaises(NameError): self.shell.run_code("func2()") - with nt.assert_raises(NameError): + with self.assertRaises(NameError): self.shell.run_code("t = Test()") - with nt.assert_raises(NameError): + with self.assertRaises(NameError): self.shell.run_code("number") # ----------- TEST NEW OBJ LOADED -------------------------- @@ -391,19 +390,19 @@ def foo(self): self.shell.magic_aimport(mod_name) stream = StringIO() self.shell.magic_aimport("", stream=stream) - nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue()) + self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue()) - with nt.assert_raises(ImportError): + with self.assertRaises(ImportError): self.shell.magic_aimport("tmpmod_as318989e89ds") else: self.shell.magic_autoreload("2") self.shell.run_code("import %s" % mod_name) stream = StringIO() self.shell.magic_aimport("", stream=stream) - nt.assert_true( + self.assertTrue( "Modules to reload:\nall-except-skipped" in stream.getvalue() ) - nt.assert_in(mod_name, self.shell.ns) + self.assertIn(mod_name, self.shell.ns) mod = sys.modules[mod_name] @@ -415,21 +414,21 @@ def foo(self): old_obj2 = mod.Bar() def check_module_contents(): - nt.assert_equal(mod.x, 9) - nt.assert_equal(mod.z, 123) + self.assertEqual(mod.x, 9) + self.assertEqual(mod.z, 123) - nt.assert_equal(old_foo(0), 3) - nt.assert_equal(mod.foo(0), 3) + self.assertEqual(old_foo(0), 3) + self.assertEqual(mod.foo(0), 3) obj = mod.Baz(9) - nt.assert_equal(old_obj.bar(1), 10) - nt.assert_equal(obj.bar(1), 10) - nt.assert_equal(obj.quux, 42) - nt.assert_equal(obj.zzz(), 99) + self.assertEqual(old_obj.bar(1), 10) + self.assertEqual(obj.bar(1), 10) + self.assertEqual(obj.quux, 42) + self.assertEqual(obj.zzz(), 99) obj2 = mod.Bar() - nt.assert_equal(old_obj2.foo(), 1) - nt.assert_equal(obj2.foo(), 1) + self.assertEqual(old_obj2.foo(), 1) + self.assertEqual(obj2.foo(), 1) check_module_contents() @@ -481,25 +480,25 @@ def foo(self): ) def check_module_contents(): - nt.assert_equal(mod.x, 10) - nt.assert_false(hasattr(mod, "z")) + self.assertEqual(mod.x, 10) + self.assertFalse(hasattr(mod, "z")) - nt.assert_equal(old_foo(0), 4) # superreload magic! - nt.assert_equal(mod.foo(0), 4) + self.assertEqual(old_foo(0), 4) # superreload magic! + self.assertEqual(mod.foo(0), 4) obj = mod.Baz(9) - nt.assert_equal(old_obj.bar(1), 11) # superreload magic! - nt.assert_equal(obj.bar(1), 11) + self.assertEqual(old_obj.bar(1), 11) # superreload magic! + self.assertEqual(obj.bar(1), 11) - nt.assert_equal(old_obj.quux, 43) - nt.assert_equal(obj.quux, 43) + self.assertEqual(old_obj.quux, 43) + self.assertEqual(obj.quux, 43) - nt.assert_false(hasattr(old_obj, "zzz")) - nt.assert_false(hasattr(obj, "zzz")) + self.assertFalse(hasattr(old_obj, "zzz")) + self.assertFalse(hasattr(obj, "zzz")) obj2 = mod.Bar() - nt.assert_equal(old_obj2.foo(), 2) - nt.assert_equal(obj2.foo(), 2) + self.assertEqual(old_obj2.foo(), 2) + self.assertEqual(obj2.foo(), 2) self.shell.run_code("pass") # trigger reload check_module_contents() @@ -519,7 +518,7 @@ def check_module_contents(): self.shell.magic_aimport("-" + mod_name) stream = StringIO() self.shell.magic_aimport("", stream=stream) - nt.assert_true(("Modules to skip:\n%s" % mod_name) in stream.getvalue()) + self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue()) # This should succeed, although no such module exists self.shell.magic_aimport("-tmpmod_as318989e89ds") @@ -546,7 +545,7 @@ def check_module_contents(): self.shell.magic_autoreload("") self.shell.run_code("pass") # trigger reload - nt.assert_equal(mod.x, -99) + self.assertEqual(mod.x, -99) def test_smoketest_aimport(self): self._check_smoketest(use_aimport=True) From f8ffa1df27b80e5ea79e69f9baf9ebfc60704009 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:05:45 +0200 Subject: [PATCH 1607/3726] [extensions][tests][storemagic] Remove nose --- IPython/extensions/tests/test_storemagic.py | 31 ++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/IPython/extensions/tests/test_storemagic.py b/IPython/extensions/tests/test_storemagic.py index 6f8371d336f..acf1abffdc2 100644 --- a/IPython/extensions/tests/test_storemagic.py +++ b/IPython/extensions/tests/test_storemagic.py @@ -1,7 +1,6 @@ import tempfile, os from traitlets.config.loader import Config -import nose.tools as nt def setup_module(): @@ -23,10 +22,10 @@ def test_store_restore(): ip.magic('store foobar foobaz') # Check storing - nt.assert_equal(ip.db['autorestore/foo'], 78) - nt.assert_in('bar', ip.db['stored_aliases']) - nt.assert_equal(ip.db['autorestore/foobar'], 79) - nt.assert_equal(ip.db['autorestore/foobaz'], '80') + assert ip.db["autorestore/foo"] == 78 + assert "bar" in ip.db["stored_aliases"] + assert ip.db["autorestore/foobar"] == 79 + assert ip.db["autorestore/foobaz"] == "80" # Remove those items ip.user_ns.pop('foo', None) @@ -37,14 +36,14 @@ def test_store_restore(): ip.user_ns['_dh'][:] = [] # Check restoring - ip.magic('store -r foo bar foobar foobaz') - nt.assert_equal(ip.user_ns['foo'], 78) - assert ip.alias_manager.is_alias('bar') - nt.assert_equal(ip.user_ns['foobar'], 79) - nt.assert_equal(ip.user_ns['foobaz'], '80') + ip.magic("store -r foo bar foobar foobaz") + assert ip.user_ns["foo"] == 78 + assert ip.alias_manager.is_alias("bar") + assert ip.user_ns["foobar"] == 79 + assert ip.user_ns["foobaz"] == "80" - ip.magic('store -r') # restores _dh too - nt.assert_in(os.path.realpath(tmpd), ip.user_ns['_dh']) + ip.magic("store -r") # restores _dh too + assert os.path.realpath(tmpd) in ip.user_ns["_dh"] os.rmdir(tmpd) @@ -57,10 +56,10 @@ def test_autorestore(): orig_config = ip.config try: ip.config = c - ip.extension_manager.reload_extension('storemagic') - nt.assert_not_in('foo', ip.user_ns) + ip.extension_manager.reload_extension("storemagic") + assert "foo" not in ip.user_ns c.StoreMagics.autorestore = True - ip.extension_manager.reload_extension('storemagic') - nt.assert_equal(ip.user_ns['foo'], 95) + ip.extension_manager.reload_extension("storemagic") + assert ip.user_ns["foo"] == 95 finally: ip.config = orig_config From 995c84486d5f176254f5d9f2762f51f05b10bc6a Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:07:03 +0200 Subject: [PATCH 1608/3726] [lib][tests][clipboard] Remove nose --- IPython/lib/tests/test_clipboard.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/IPython/lib/tests/test_clipboard.py b/IPython/lib/tests/test_clipboard.py index f1050bfb5a6..802f753a339 100644 --- a/IPython/lib/tests/test_clipboard.py +++ b/IPython/lib/tests/test_clipboard.py @@ -1,5 +1,3 @@ -import nose.tools as nt - from IPython.core.error import TryNext from IPython.lib.clipboard import ClipboardEmpty from IPython.testing.decorators import skip_if_no_x11 @@ -18,4 +16,4 @@ def test_clipboard_get(): # No clipboard access API available pass else: - nt.assert_is_instance(a, str) + assert isinstance(a, str) From a1ddd3bc4b72c913bdb95f01cb04c39391b0bc76 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:13:34 +0200 Subject: [PATCH 1609/3726] [lib][tests][deepreload] Remove nose --- IPython/lib/tests/test_deepreload.py | 6 ++--- IPython/lib/tests/test_display.py | 34 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/IPython/lib/tests/test_deepreload.py b/IPython/lib/tests/test_deepreload.py index 992ebab73ac..d9af0f1dbbd 100644 --- a/IPython/lib/tests/test_deepreload.py +++ b/IPython/lib/tests/test_deepreload.py @@ -6,8 +6,6 @@ from pathlib import Path -import nose.tools as nt - from IPython.utils.syspathcontext import prepended_to_syspath from IPython.utils.tempdir import TemporaryDirectory from IPython.lib.deepreload import reload as dreload @@ -28,9 +26,9 @@ def test_deepreload(): # Test that A is not reloaded. obj = A.Object() dreload(B, exclude=["A"]) - nt.assert_true(isinstance(obj, A.Object)) + assert isinstance(obj, A.Object) is True # Test that A is reloaded. obj = A.Object() dreload(B) - nt.assert_false(isinstance(obj, A.Object)) + assert isinstance(obj, A.Object) is False diff --git a/IPython/lib/tests/test_display.py b/IPython/lib/tests/test_display.py index 414d3fd2b8c..f5ed34c912b 100644 --- a/IPython/lib/tests/test_display.py +++ b/IPython/lib/tests/test_display.py @@ -21,7 +21,7 @@ from io import BytesIO # Third-party imports -import nose.tools as nt +import pytest try: import numpy @@ -48,10 +48,10 @@ def test_instantiation_FileLink(): fl = display.FileLink(pathlib.PurePath('example.txt')) def test_warning_on_non_existent_path_FileLink(): - """FileLink: Calling _repr_html_ on non-existent files returns a warning - """ - fl = display.FileLink('example.txt') - nt.assert_true(fl._repr_html_().startswith('Path (example.txt)')) + """FileLink: Calling _repr_html_ on non-existent files returns a warning""" + fl = display.FileLink("example.txt") + assert fl._repr_html_().startswith("Path (example.txt)") + def test_existing_path_FileLink(): """FileLink: Calling _repr_html_ functions as expected on existing filepath @@ -77,7 +77,7 @@ def test_error_on_directory_to_FileLink(): """FileLink: Raises error when passed directory """ td = mkdtemp() - nt.assert_raises(ValueError,display.FileLink,td) + pytest.raises(ValueError, display.FileLink, td) #-------------------------- # FileLinks tests @@ -89,10 +89,10 @@ def test_instantiation_FileLinks(): fls = display.FileLinks('example') def test_warning_on_non_existent_path_FileLinks(): - """FileLinks: Calling _repr_html_ on non-existent files returns a warning - """ - fls = display.FileLinks('example') - nt.assert_true(fls._repr_html_().startswith('Path (example)')) + """FileLinks: Calling _repr_html_ on non-existent files returns a warning""" + fls = display.FileLinks("example") + assert fls._repr_html_().startswith("Path (example)") + def test_existing_path_FileLinks(): """FileLinks: Calling _repr_html_ functions as expected on existing dir @@ -172,7 +172,8 @@ def test_error_on_file_to_FileLinks(): """ td = mkdtemp() tf1 = NamedTemporaryFile(dir=td) - nt.assert_raises(ValueError,display.FileLinks,tf1.name) + pytest.raises(ValueError, display.FileLinks, tf1.name) + def test_recursive_FileLinks(): """FileLinks: Does not recurse when recursive=False @@ -210,7 +211,7 @@ def test_audio_from_list(self): @skipif_not_numpy def test_audio_from_numpy_array_without_rate_raises(self): - nt.assert_raises(ValueError, display.Audio, get_test_tone()) + self.assertRaises(ValueError, display.Audio, get_test_tone()) @skipif_not_numpy def test_audio_data_normalization(self): @@ -232,10 +233,10 @@ def test_audio_data_without_normalization(self): assert actual_max_value == expected_max_value def test_audio_data_without_normalization_raises_for_invalid_data(self): - nt.assert_raises( + self.assertRaises( ValueError, lambda: display.Audio([1.001], rate=44100, normalize=False)) - nt.assert_raises( + self.assertRaises( ValueError, lambda: display.Audio([-1.001], rate=44100, normalize=False)) @@ -253,9 +254,8 @@ class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy): @skipif_not_numpy def test_audio_raises_for_nested_list(self): stereo_signal = [list(get_test_tone())] * 2 - nt.assert_raises( - TypeError, - lambda: display.Audio(stereo_signal, rate=44100)) + self.assertRaises(TypeError, lambda: display.Audio(stereo_signal, rate=44100)) + @skipif_not_numpy def get_test_tone(scale=1): From e01e33248d71fb5555b277dcd84dc9b28b1311bd Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:14:05 +0200 Subject: [PATCH 1610/3726] [lib][tests][latextools] Remove nose --- IPython/lib/tests/test_latextools.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/IPython/lib/tests/test_latextools.py b/IPython/lib/tests/test_latextools.py index fafff73d153..60a7589b508 100644 --- a/IPython/lib/tests/test_latextools.py +++ b/IPython/lib/tests/test_latextools.py @@ -5,7 +5,6 @@ from contextlib import contextmanager from unittest.mock import patch -import nose.tools as nt import pytest from IPython.lib import latextools @@ -185,7 +184,13 @@ def test_latex_to_png_invalid_hex_colors(): Test that invalid hex colors provided to dvipng gives an exception. """ latex_string = "$x^2$" - nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string, - backend='dvipng', color="#f00bar")) - nt.assert_raises(ValueError, lambda: latextools.latex_to_png(latex_string, - backend='dvipng', color="#f00")) + pytest.raises( + ValueError, + lambda: latextools.latex_to_png( + latex_string, backend="dvipng", color="#f00bar" + ), + ) + pytest.raises( + ValueError, + lambda: latextools.latex_to_png(latex_string, backend="dvipng", color="#f00"), + ) From 3c04a296aa67cab9a88193d603e1be3e76dc838c Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:15:17 +0200 Subject: [PATCH 1611/3726] [lib][tests][pretty] Remove nose --- IPython/lib/tests/test_pretty.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 3c8cbf201b3..8c116300b34 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -11,7 +11,6 @@ import string import unittest -import nose.tools as nt import pytest from IPython.lib import pretty @@ -70,7 +69,6 @@ def __repr__(self): return "Breaking(\n)" class BadRepr(object): - def __repr__(self): return 1/0 @@ -178,7 +176,7 @@ def test_pprint_break_repr(): def test_bad_repr(): """Don't catch bad repr errors""" - with nt.assert_raises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): pretty.pretty(BadRepr()) class BadException(Exception): @@ -190,12 +188,12 @@ class ReallyBadRepr(object): @property def __class__(self): raise ValueError("I am horrible") - + def __repr__(self): raise BadException() def test_really_bad_repr(): - with nt.assert_raises(BadException): + with pytest.raises(BadException): pretty.pretty(ReallyBadRepr()) @@ -266,11 +264,11 @@ def test_metaclass_repr(): def test_unicode_repr(): u = u"üniçodé" ustr = u - + class C(object): def __repr__(self): return ustr - + c = C() p = pretty.pretty(c) assert p == u @@ -293,7 +291,7 @@ def type_pprint_wrapper(obj, p, cycle): output = stream.getvalue() assert output == "%s.MyObj" % __name__ - nt.assert_true(type_pprint_wrapper.called) + assert type_pprint_wrapper.called is True # TODO : pytest.mark.parametrise once nose is gone. @@ -478,7 +476,7 @@ def meaning_of_life(question=None): return 42 return "Don't panic" - nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life)) + assert "meaning_of_life(question=None)" in pretty.pretty(meaning_of_life) class OrderedCounter(Counter, OrderedDict): @@ -497,6 +495,6 @@ def __repr__(self): def test_custom_repr(): """A custom repr should override a pretty printer for a parent type""" oc = OrderedCounter("abracadabra") - nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc)) + assert "OrderedCounter(OrderedDict" in pretty.pretty(oc) assert pretty.pretty(MySet()) == "mine" From a167c8b57ba7ac6460652a75015f5f82d751a75b Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:15:52 +0200 Subject: [PATCH 1612/3726] [terminal][tests][embed] Remove nose --- IPython/terminal/tests/test_embed.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/IPython/terminal/tests/test_embed.py b/IPython/terminal/tests/test_embed.py index 98dd94653ab..ac4c9424427 100644 --- a/IPython/terminal/tests/test_embed.py +++ b/IPython/terminal/tests/test_embed.py @@ -14,7 +14,7 @@ import os import subprocess import sys -import nose.tools as nt + from IPython.utils.tempdir import NamedFileInTemporaryDirectory from IPython.testing.decorators import skip_win32 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE @@ -55,12 +55,13 @@ def test_ipython_embed(): out, err = p.communicate(_exit) std = out.decode('UTF-8') - nt.assert_equal(p.returncode, 0) - nt.assert_in('3 . 14', std) - if os.name != 'nt': + assert p.returncode == 0 + assert "3 . 14" in std + if os.name != "nt": # TODO: Fix up our different stdout references, see issue gh-14 - nt.assert_in('IPython', std) - nt.assert_in('bye!', std) + assert "IPython" in std + assert "bye!" in std + @skip_win32 def test_nest_embed(): From 66e3f6ee970edd3d4dc292e1489a7e2bb16579a3 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:17:20 +0200 Subject: [PATCH 1613/3726] [terminal][tests][interactiveshell] Remove nose --- .../terminal/tests/test_interactivshell.py | 70 ++++++++++++++----- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/IPython/terminal/tests/test_interactivshell.py b/IPython/terminal/tests/test_interactivshell.py index 640e5d482ae..763ccc96917 100644 --- a/IPython/terminal/tests/test_interactivshell.py +++ b/IPython/terminal/tests/test_interactivshell.py @@ -12,29 +12,42 @@ from IPython.utils.capture import capture_output from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context -import nose.tools as nt -class TestElide(unittest.TestCase): +class TestElide(unittest.TestCase): def test_elide(self): - _elide('concatenate((a1, a2, ...), axis', '') # do not raise - _elide('concatenate((a1, a2, ..), . axis', '') # do not raise - nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh',''), 'aaaa.b…g.hhhhhh') - - test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', '']) - expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c' - nt.assert_equal(_elide(test_string, ''), expect_stirng) + _elide("concatenate((a1, a2, ...), axis", "") # do not raise + _elide("concatenate((a1, a2, ..), . axis", "") # do not raise + self.assertEqual( + _elide("aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh", ""), + "aaaa.b…g.hhhhhh", + ) + + test_string = os.sep.join(["", 10 * "a", 10 * "b", 10 * "c", ""]) + expect_stirng = ( + os.sep + "a" + "\N{HORIZONTAL ELLIPSIS}" + "b" + os.sep + 10 * "c" + ) + self.assertEqual(_elide(test_string, ""), expect_stirng) def test_elide_typed_normal(self): - nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick brown fox', min_elide=10), 'the…fox jumped over the lazy dog') - + self.assertEqual( + _elide( + "the quick brown fox jumped over the lazy dog", + "the quick brown fox", + min_elide=10, + ), + "the…fox jumped over the lazy dog", + ) def test_elide_typed_short_match(self): """ if the match is too short we don't elide. avoid the "the...the" """ - nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the', min_elide=10), 'the quick brown fox jumped over the lazy dog') + self.assertEqual( + _elide("the quick brown fox jumped over the lazy dog", "the", min_elide=10), + "the quick brown fox jumped over the lazy dog", + ) def test_elide_typed_no_match(self): """ @@ -42,22 +55,41 @@ def test_elide_typed_no_match(self): avoid the "the...the" """ # here we typed red instead of brown - nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick red fox', min_elide=10), 'the quick brown fox jumped over the lazy dog') + self.assertEqual( + _elide( + "the quick brown fox jumped over the lazy dog", + "the quick red fox", + min_elide=10, + ), + "the quick brown fox jumped over the lazy dog", + ) -class TestContextAwareCompletion(unittest.TestCase): +class TestContextAwareCompletion(unittest.TestCase): def test_adjust_completion_text_based_on_context(self): # Adjusted case - nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1') + self.assertEqual( + _adjust_completion_text_based_on_context("arg1=", "func1(a=)", 7), "arg1" + ) # Untouched cases - nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=') - nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=') - nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic') - nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2') + self.assertEqual( + _adjust_completion_text_based_on_context("arg1=", "func1(a)", 7), "arg1=" + ) + self.assertEqual( + _adjust_completion_text_based_on_context("arg1=", "func1(a", 7), "arg1=" + ) + self.assertEqual( + _adjust_completion_text_based_on_context("%magic", "func1(a=)", 7), "%magic" + ) + self.assertEqual( + _adjust_completion_text_based_on_context("func2", "func1(a=)", 7), "func2" + ) + # Decorator for interaction loop tests ----------------------------------------- + class mock_input_helper(object): """Machinery for tests of the main interact loop. From b7155f665a91c65365b7a82d5daeb9fdec9b0722 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:17:47 +0200 Subject: [PATCH 1614/3726] [utils][tests][dir2] Remove nose --- IPython/utils/tests/test_dir2.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/IPython/utils/tests/test_dir2.py b/IPython/utils/tests/test_dir2.py index bbebf60aed8..20c601e3b86 100644 --- a/IPython/utils/tests/test_dir2.py +++ b/IPython/utils/tests/test_dir2.py @@ -1,4 +1,3 @@ -import nose.tools as nt from IPython.utils.dir2 import dir2 @@ -9,12 +8,13 @@ class Base(object): def test_base(): res = dir2(Base()) - assert ('x' in res) - assert ('z' in res) - assert ('y' not in res) - assert ('__class__' in res) - nt.assert_equal(res.count('x'), 1) - nt.assert_equal(res.count('__class__'), 1) + assert "x" in res + assert "z" in res + assert "y" not in res + assert "__class__" in res + assert res.count("x") == 1 + assert res.count("__class__") == 1 + def test_SubClass(): @@ -22,9 +22,9 @@ class SubClass(Base): y = 2 res = dir2(SubClass()) - assert ('y' in res) - nt.assert_equal(res.count('y'), 1) - nt.assert_equal(res.count('x'), 1) + assert "y" in res + assert res.count("y") == 1 + assert res.count("x") == 1 def test_SubClass_with_trait_names_attr(): From 77e81773ca867b9e90a788a7cb3875d4dd4a5bff Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:19:30 +0200 Subject: [PATCH 1615/3726] [utils][tests][importstring] Remove nose --- IPython/utils/tests/test_importstring.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/IPython/utils/tests/test_importstring.py b/IPython/utils/tests/test_importstring.py index 0c79cb3cf08..007fd0d26e5 100644 --- a/IPython/utils/tests/test_importstring.py +++ b/IPython/utils/tests/test_importstring.py @@ -11,7 +11,7 @@ # Imports #----------------------------------------------------------------------------- -import nose.tools as nt +import pytest from IPython.utils.importstring import import_item @@ -22,18 +22,19 @@ def test_import_plain(): "Test simple imports" import os - os2 = import_item('os') - nt.assert_true(os is os2) + + os2 = import_item("os") + assert os is os2 def test_import_nested(): "Test nested imports from the stdlib" from os import path - path2 = import_item('os.path') - nt.assert_true(path is path2) + + path2 = import_item("os.path") + assert path is path2 def test_import_raises(): "Test that failing imports raise the right exception" - nt.assert_raises(ImportError, import_item, 'IPython.foobar') - + pytest.raises(ImportError, import_item, "IPython.foobar") From c8161eb28b10cb73eb6f8497c37a87966ce053da Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:20:04 +0200 Subject: [PATCH 1616/3726] [utils][tests][io] Remove nose --- IPython/utils/tests/test_io.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/IPython/utils/tests/test_io.py b/IPython/utils/tests/test_io.py index 83367fa9e9f..fa3abab748b 100644 --- a/IPython/utils/tests/test_io.py +++ b/IPython/utils/tests/test_io.py @@ -11,8 +11,6 @@ from subprocess import Popen, PIPE import unittest -import nose.tools as nt - from IPython.utils.io import IOStream, Tee, capture_output @@ -22,7 +20,7 @@ def test_tee_simple(): text = 'Hello' tee = Tee(chan, channel='stdout') print(text, file=chan) - nt.assert_equal(chan.getvalue(), text+"\n") + assert chan.getvalue() == text + "\n" class TeeTestCase(unittest.TestCase): @@ -39,7 +37,7 @@ def tchan(self, channel): print(text, end='', file=chan) trap_val = trap.getvalue() - nt.assert_equal(chan.getvalue(), text) + self.assertEqual(chan.getvalue(), text) tee.close() @@ -82,8 +80,8 @@ def test_capture_output(self): """capture_output() context works""" with capture_output() as io: - print('hi, stdout') - print('hi, stderr', file=sys.stderr) - - nt.assert_equal(io.stdout, 'hi, stdout\n') - nt.assert_equal(io.stderr, 'hi, stderr\n') + print("hi, stdout") + print("hi, stderr", file=sys.stderr) + + self.assertEqual(io.stdout, "hi, stdout\n") + self.assertEqual(io.stderr, "hi, stderr\n") From 691cde99a8f3b0ffa8837c3ca5055a6c35eea6ce Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:20:21 +0200 Subject: [PATCH 1617/3726] [utils][tests][module_paths] Remove nose --- IPython/utils/tests/test_module_paths.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/IPython/utils/tests/test_module_paths.py b/IPython/utils/tests/test_module_paths.py index ef2841bb34d..12679990c0c 100644 --- a/IPython/utils/tests/test_module_paths.py +++ b/IPython/utils/tests/test_module_paths.py @@ -22,8 +22,6 @@ import IPython.utils.module_paths as mp -import nose.tools as nt - TEST_FILE_PATH = Path(__file__).resolve().parent TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot")) @@ -67,7 +65,7 @@ def test_tempdir(): """ Ensure the test are done with a temporary file that have a dot somewhere. """ - nt.assert_in(".", str(TMP_TEST_DIR)) + assert "." in str(TMP_TEST_DIR) def test_find_mod_1(): @@ -76,7 +74,7 @@ def test_find_mod_1(): Expected output: a path to that directory's __init__.py file. """ modpath = TMP_TEST_DIR / "xmod" / "__init__.py" - nt.assert_equal(Path(mp.find_mod("xmod")), modpath) + assert Path(mp.find_mod("xmod")) == modpath def test_find_mod_2(): """ @@ -85,7 +83,7 @@ def test_find_mod_2(): TODO: Confirm why this is a duplicate test. """ modpath = TMP_TEST_DIR / "xmod" / "__init__.py" - nt.assert_equal(Path(mp.find_mod("xmod")), modpath) + assert Path(mp.find_mod("xmod")) == modpath def test_find_mod_3(): """ @@ -93,7 +91,7 @@ def test_find_mod_3(): Expected output: full path with .py extension. """ modpath = TMP_TEST_DIR / "xmod" / "sub.py" - nt.assert_equal(Path(mp.find_mod("xmod.sub")), modpath) + assert Path(mp.find_mod("xmod.sub")) == modpath def test_find_mod_4(): """ @@ -101,11 +99,11 @@ def test_find_mod_4(): Expected output: full path with .py extension """ modpath = TMP_TEST_DIR / "pack.py" - nt.assert_equal(Path(mp.find_mod("pack")), modpath) + assert Path(mp.find_mod("pack")) == modpath def test_find_mod_5(): """ Search for a filename with a .pyc extension Expected output: TODO: do we exclude or include .pyc files? """ - nt.assert_equal(mp.find_mod("packpyc"), None) + assert mp.find_mod("packpyc") == None From 9c38a13d5c95503c6007fcc6affee7b6bc18ae36 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:22:07 +0200 Subject: [PATCH 1618/3726] [utils][tests][openpy] Remove nose --- IPython/utils/tests/test_openpy.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/IPython/utils/tests/test_openpy.py b/IPython/utils/tests/test_openpy.py index 9e66d43df3e..e205f06ace3 100644 --- a/IPython/utils/tests/test_openpy.py +++ b/IPython/utils/tests/test_openpy.py @@ -1,6 +1,5 @@ import io import os.path -import nose.tools as nt from IPython.utils import openpy @@ -11,14 +10,14 @@ def test_detect_encoding(): with open(nonascii_path, "rb") as f: enc, lines = openpy.detect_encoding(f.readline) - nt.assert_equal(enc, "iso-8859-5") + assert enc == "iso-8859-5" def test_read_file(): with io.open(nonascii_path, encoding="iso-8859-5") as f: read_specified_enc = f.read() read_detected_enc = openpy.read_py_file(nonascii_path, skip_encoding_cookie=False) - nt.assert_equal(read_detected_enc, read_specified_enc) + assert read_detected_enc == read_specified_enc assert "coding: iso-8859-5" in read_detected_enc read_strip_enc_cookie = openpy.read_py_file( @@ -30,10 +29,10 @@ def test_read_file(): def test_source_to_unicode(): with io.open(nonascii_path, "rb") as f: source_bytes = f.read() - nt.assert_equal( - openpy.source_to_unicode(source_bytes, skip_encoding_cookie=False).splitlines(), - source_bytes.decode("iso-8859-5").splitlines(), + assert ( + openpy.source_to_unicode(source_bytes, skip_encoding_cookie=False).splitlines() + == source_bytes.decode("iso-8859-5").splitlines() ) source_no_cookie = openpy.source_to_unicode(source_bytes, skip_encoding_cookie=True) - nt.assert_not_in("coding: iso-8859-5", source_no_cookie) + assert "coding: iso-8859-5" not in source_no_cookie From c179da012af2a93c09c388cdd0f1c423a99ba4ea Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:22:31 +0200 Subject: [PATCH 1619/3726] [utils][tests][path] Remove the use of nt Needs more work to remove all nose related code --- IPython/utils/tests/test_path.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index a7be2be885d..bca72f4666e 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -15,7 +15,7 @@ from imp import reload from nose import SkipTest, with_setup -import nose.tools as nt +import pytest import IPython from IPython import paths @@ -155,7 +155,7 @@ def test_get_home_dir_5(): env['HOME'] = abspath(HOME_TEST_DIR+'garbage') # set os.name = posix, to prevent My Documents fallback on Windows os.name = 'posix' - nt.assert_raises(path.HomeDirError, path.get_home_dir, True) + pytest.raises(path.HomeDirError, path.get_home_dir, True) # Should we stub wreg fully so we can run the test on all platforms? @skip_if_not_win32 @@ -237,8 +237,7 @@ def test_get_xdg_dir_3(): env.pop('IPYTHONDIR', None) env.pop('XDG_CONFIG_HOME', None) cfgdir=os.path.join(path.get_home_dir(), '.config') - if not os.path.exists(cfgdir): - os.makedirs(cfgdir) + os.makedirs(cfgdir, exist_ok=True) assert path.get_xdg_dir() is None @@ -305,15 +304,15 @@ def test_get_py_filename(): assert path.get_py_filename("foo") == "foo.py" with make_tempfile("foo"): assert path.get_py_filename("foo") == "foo" - nt.assert_raises(IOError, path.get_py_filename, "foo.py") - nt.assert_raises(IOError, path.get_py_filename, "foo") - nt.assert_raises(IOError, path.get_py_filename, "foo.py") + pytest.raises(IOError, path.get_py_filename, "foo.py") + pytest.raises(IOError, path.get_py_filename, "foo") + pytest.raises(IOError, path.get_py_filename, "foo.py") true_fn = "foo with spaces.py" with make_tempfile(true_fn): assert path.get_py_filename("foo with spaces") == true_fn assert path.get_py_filename("foo with spaces.py") == true_fn - nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"') - nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'") + pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"') + pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'") @onlyif_unicode_paths def test_unicode_in_filename(): @@ -414,7 +413,7 @@ def test_ensure_dir_exists(): path.ensure_dir_exists(d) # no-op f = os.path.join(td, 'ƒile') open(f, 'w').close() # touch - with nt.assert_raises(IOError): + with pytest.raises(IOError): path.ensure_dir_exists(f) class TestLinkOrCopy(unittest.TestCase): From 88d2ced99afc77004a1fb765af0d1d4404470cd0 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:23:10 +0200 Subject: [PATCH 1620/3726] [utils][tests][process] Remove nose --- IPython/utils/tests/test_process.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index c53999ebd25..f4ee3859ed8 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -20,9 +20,8 @@ import time from _thread import interrupt_main # Py 3 import threading -from unittest import SkipTest -import nose.tools as nt +import pytest from IPython.utils.process import (find_cmd, FindCmdError, arg_split, system, getoutput, getoutputerror, @@ -41,8 +40,8 @@ @dec.skip_win32 def test_find_cmd_ls(): """Make sure we can find the full path to ls.""" - path = find_cmd('ls') - nt.assert_true(path.endswith('ls')) + path = find_cmd("ls") + assert path.endswith("ls") def has_pywin32(): @@ -64,7 +63,7 @@ def test_find_cmd_pythonw(): "This test runs on posix or in win32 with win32api installed") def test_find_cmd_fail(): """Make sure that FindCmdError is raised if we can't find the cmd.""" - nt.assert_raises(FindCmdError,find_cmd,'asdfasdf') + pytest.raises(FindCmdError, find_cmd, "asdfasdf") # TODO: move to pytest.mark.parametrize once nose gone @@ -121,7 +120,7 @@ def assert_interrupts(self, command): Interrupt a subprocess after a second. """ if threading.main_thread() != threading.current_thread(): - raise nt.SkipTest("Can't run this test if not in main thread.") + raise pytest.skip("Can't run this test if not in main thread.") # Some tests can overwrite SIGINT handler (by using pdb for example), # which then breaks this test, so just make sure it's operating From bd2c171ee99c7afd51b4eb7f85fd83f6b9116e57 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:23:24 +0200 Subject: [PATCH 1621/3726] [utils][tests][pycolorize] Remove nose --- IPython/utils/tests/test_pycolorize.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/IPython/utils/tests/test_pycolorize.py b/IPython/utils/tests/test_pycolorize.py index c3502f32a49..178f4a98c47 100644 --- a/IPython/utils/tests/test_pycolorize.py +++ b/IPython/utils/tests/test_pycolorize.py @@ -17,9 +17,6 @@ # Imports #----------------------------------------------------------------------------- -# third party -import nose.tools as nt - from IPython.testing.decorators import skip_iptest_but_not_pytest # our own @@ -65,7 +62,7 @@ def test_parse_sample(style): buf.seek(0) f1 = buf.read() - nt.assert_not_in("ERROR", f1) + assert "ERROR" not in f1 @skip_iptest_but_not_pytest @@ -73,4 +70,4 @@ def test_parse_error(style): p = Parser(style=style) f1 = p.format(")", "str") if style != "NoColor": - nt.assert_in("ERROR", f1) + assert "ERROR" in f1 From 8be458e9bb7a51d205effce5f11da84514450aea Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:24:32 +0200 Subject: [PATCH 1622/3726] [utils][tests][sysinfo] Remove nose --- IPython/utils/tests/test_sysinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/IPython/utils/tests/test_sysinfo.py b/IPython/utils/tests/test_sysinfo.py index c3f879b32f4..23f80e4e5ff 100644 --- a/IPython/utils/tests/test_sysinfo.py +++ b/IPython/utils/tests/test_sysinfo.py @@ -5,7 +5,6 @@ # Distributed under the terms of the Modified BSD License. import json -import nose.tools as nt from IPython.utils import sysinfo From 45fe9d82996fb4781a2de79bf5aecc26b0eb20ed Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:25:14 +0200 Subject: [PATCH 1623/3726] [utils][tests][text] Remove nose --- IPython/utils/tests/test_text.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/IPython/utils/tests/test_text.py b/IPython/utils/tests/test_text.py index 8f9b73c167e..72660d4826c 100644 --- a/IPython/utils/tests/test_text.py +++ b/IPython/utils/tests/test_text.py @@ -17,9 +17,10 @@ import random import sys -import nose.tools as nt from pathlib import Path +import pytest + from IPython.utils import text #----------------------------------------------------------------------------- @@ -116,8 +117,9 @@ def eval_formatter_check(f): assert s == ns["u"] # This decodes in a platform dependent manner, but it shouldn't error out s = f.format("{b}", **ns) - - nt.assert_raises(NameError, f.format, '{dne}', **ns) + + pytest.raises(NameError, f.format, "{dne}", **ns) + def eval_formatter_slicing_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) @@ -128,7 +130,7 @@ def eval_formatter_slicing_check(f): s = f.format("{stuff[::2]}", **ns) assert s == ns["stuff"][::2] - nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns) + pytest.raises(SyntaxError, f.format, "{n:x}", **ns) def eval_formatter_no_slicing_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) @@ -194,7 +196,7 @@ def test_LSString(): assert lss.l == ["abc", "def"] assert lss.s == "abc def" lss = text.LSString(os.getcwd()) - nt.assert_is_instance(lss.p[0], Path) + assert isinstance(lss.p[0], Path) def test_SList(): sl = text.SList(["a 11", "b 1", "a 2"]) From 01716cb9888d520ff00fe447897b14af327bd329 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 13:25:34 +0200 Subject: [PATCH 1624/3726] [utils][tests][tokenutil] Remove nose --- IPython/utils/tests/test_tokenutil.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/IPython/utils/tests/test_tokenutil.py b/IPython/utils/tests/test_tokenutil.py index 46b6631da9c..406c0ae51fa 100644 --- a/IPython/utils/tests/test_tokenutil.py +++ b/IPython/utils/tests/test_tokenutil.py @@ -2,7 +2,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import nose.tools as nt import pytest from IPython.testing.decorators import skip_iptest_but_not_pytest @@ -17,13 +16,16 @@ def expect_token(expected, cell, cursor_pos): else: offset += len(line)+1 column = cursor_pos - offset - line_with_cursor = '%s|%s' % (line[:column], line[column:]) - nt.assert_equal(token, expected, - "Expected %r, got %r in: %r (pos %i)" % ( - expected, token, line_with_cursor, cursor_pos) + line_with_cursor = "%s|%s" % (line[:column], line[column:]) + assert token == expected, "Expected %r, got %r in: %r (pos %i)" % ( + expected, + token, + line_with_cursor, + cursor_pos, ) -def test_simple(): + +def test_simple(): cell = "foo" for i in range(len(cell)): expect_token("foo", cell, i) @@ -107,20 +109,20 @@ def test_attrs(): def test_line_at_cursor(): cell = "" (line, offset) = line_at_cursor(cell, cursor_pos=11) - nt.assert_equal(line, "") - nt.assert_equal(offset, 0) + assert line == "" + assert offset == 0 # The position after a newline should be the start of the following line. cell = "One\nTwo\n" (line, offset) = line_at_cursor(cell, cursor_pos=4) - nt.assert_equal(line, "Two\n") - nt.assert_equal(offset, 4) + assert line == "Two\n" + assert offset == 4 # The end of a cell should be on the last line cell = "pri\npri" (line, offset) = line_at_cursor(cell, cursor_pos=7) - nt.assert_equal(line, "pri") - nt.assert_equal(offset, 4) + assert line == "pri" + assert offset == 4 @pytest.mark.parametrize( From 7fb86c59f29efcdd7059459f5fd614bc9fde68e7 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Sat, 23 Oct 2021 15:30:05 +0200 Subject: [PATCH 1625/3726] [utils][tests][dir2] Improve test_misbehaving_object_without_trait_names --- IPython/utils/tests/test_dir2.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/IPython/utils/tests/test_dir2.py b/IPython/utils/tests/test_dir2.py index 20c601e3b86..d35b110e413 100644 --- a/IPython/utils/tests/test_dir2.py +++ b/IPython/utils/tests/test_dir2.py @@ -1,5 +1,7 @@ from IPython.utils.dir2 import dir2 +import pytest + class Base(object): x = 1 @@ -35,24 +37,31 @@ class SubClass(Base): trait_names = 44 res = dir2(SubClass()) - assert('trait_names' in res) + assert "trait_names" in res def test_misbehaving_object_without_trait_names(): # dir2 shouldn't raise even when objects are dumb and raise # something other than AttribteErrors on bad getattr. - class MisbehavingGetattr(object): - def __getattr__(self): + class MisbehavingGetattr: + def __getattr__(self, attr): raise KeyError("I should be caught") def some_method(self): - pass + return True class SillierWithDir(MisbehavingGetattr): def __dir__(self): return ['some_method'] for bad_klass in (MisbehavingGetattr, SillierWithDir): - res = dir2(bad_klass()) - assert('some_method' in res) + obj = bad_klass() + + assert obj.some_method() + + with pytest.raises(KeyError): + obj.other_method() + + res = dir2(obj) + assert "some_method" in res From 0083c5397fafb0e1650e3b0739bc168a709d5928 Mon Sep 17 00:00:00 2001 From: "Paulo S. Costa" Date: Sun, 24 Oct 2021 07:55:06 -0700 Subject: [PATCH 1626/3726] Adhere to usage style when documenting save args --- IPython/core/magics/code.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index e4b85e3b6b6..1451bd56488 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -185,7 +185,7 @@ def save(self, parameter_s=''): """Save a set of lines or a macro to a given filename. Usage:\\ - %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ... + %save [options] filename [history] Options: @@ -199,7 +199,7 @@ def save(self, parameter_s=''): -a: append to the file instead of overwriting it. - This function uses the same syntax as %history for input ranges, + The history argument uses the same syntax as %history for input ranges, then saves the lines to the filename you specify. If no ranges are specified, saves history of the current session up to From ac1cb9ab7b1bc13f68a7cb979971b9ebaae93fb2 Mon Sep 17 00:00:00 2001 From: Naelson Douglas Date: Mon, 25 Oct 2021 00:11:13 -0300 Subject: [PATCH 1627/3726] removed a name shadowing pitfall --- setupbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setupbase.py b/setupbase.py index 7befb88bf33..e6330ea11c5 100644 --- a/setupbase.py +++ b/setupbase.py @@ -95,8 +95,8 @@ def find_packages(): """ excludes = ['deathrow', 'quarantine'] packages = [] - for dir,subdirs,files in os.walk('IPython'): - package = dir.replace(os.path.sep, '.') + for directory, subdirs,files in os.walk('IPython'): + package = directory.replace(os.path.sep, '.') if any(package.startswith('IPython.'+exc) for exc in excludes): # package is to be excluded (e.g. deathrow) continue From 7c15dbcdb985ec98cdfec447e7b078c4d3f0eb86 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:47:54 +0300 Subject: [PATCH 1628/3726] Mark passwd doctest example as random output --- IPython/lib/security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/lib/security.py b/IPython/lib/security.py index 91a2344eab8..152561dabad 100644 --- a/IPython/lib/security.py +++ b/IPython/lib/security.py @@ -48,7 +48,7 @@ def passwd(passphrase=None, algorithm='sha1'): Examples -------- >>> passwd('mypassword') - 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12' + 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12' # random """ if passphrase is None: From 1720e0629c74dd02046c02a8e6ca56c4f8a28be9 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:48:25 +0300 Subject: [PATCH 1629/3726] Make OS echo example Windows-compatible --- IPython/testing/plugin/test_exampleip.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/testing/plugin/test_exampleip.txt b/IPython/testing/plugin/test_exampleip.txt index 8afcbfdf7d8..96b1eae19f0 100644 --- a/IPython/testing/plugin/test_exampleip.txt +++ b/IPython/testing/plugin/test_exampleip.txt @@ -21,7 +21,7 @@ Another example:: Just like in IPython docstrings, you can use all IPython syntax and features:: - In [9]: !echo "hello" + In [9]: !echo hello hello In [10]: a='hi' From 8f0d50b7125b1c2cf2179d33fac03da5cd715e55 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:49:16 +0300 Subject: [PATCH 1630/3726] Fix escaping warning in _get_long_path_name doctest --- IPython/utils/path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/utils/path.py b/IPython/utils/path.py index b347e98ed46..273c519a004 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -33,7 +33,7 @@ def _get_long_path_name(path): Examples -------- - >>> get_long_path_name('c:\\docume~1') + >>> get_long_path_name('c:\\\\docume~1') 'c:\\\\Documents and Settings' """ From cee163f6d219ff013dbb9ff888baee18fb80d814 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:49:50 +0300 Subject: [PATCH 1631/3726] Fix IPythonConsoleLexer doctest --- IPython/lib/lexers.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/IPython/lib/lexers.py b/IPython/lib/lexers.py index 4494da56571..0c9b6e1bc7e 100644 --- a/IPython/lib/lexers.py +++ b/IPython/lib/lexers.py @@ -221,11 +221,9 @@ class IPythonConsoleLexer(Lexer): In [2]: a Out[2]: 'foo' - In [3]: print a + In [3]: print(a) foo - In [4]: 1 / 0 - Support is also provided for IPython exceptions: @@ -234,13 +232,9 @@ class IPythonConsoleLexer(Lexer): .. code-block:: ipythonconsole In [1]: raise Exception - - --------------------------------------------------------------------------- - Exception Traceback (most recent call last) - in - ----> 1 raise Exception - - Exception: + Traceback (most recent call last): + ... + Exception """ name = 'IPython console session' From 02a8decad7e8a51d56c91e9c8294f570864d0cac Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:53:41 +0300 Subject: [PATCH 1632/3726] Do not run POSIX-specific doctest on Windows --- IPython/testing/plugin/dtexample.py | 34 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/IPython/testing/plugin/dtexample.py b/IPython/testing/plugin/dtexample.py index d73cd246fd2..119e0a0536c 100644 --- a/IPython/testing/plugin/dtexample.py +++ b/IPython/testing/plugin/dtexample.py @@ -4,6 +4,9 @@ All tests should be loaded by nose. """ +import os + + def pyfunc(): """Some pure python tests... @@ -38,18 +41,6 @@ def ipfunc(): 0 1 1 2 2 3 - Examples that access the operating system work: - - In [1]: !echo hello - hello - - In [2]: !echo hello > /tmp/foo_iptest - - In [3]: !cat /tmp/foo_iptest - hello - - In [4]: rm -f /tmp/foo_iptest - It's OK to use '_' for the last result, but do NOT try to use IPython's numbered history of _NN outputs, since those won't exist under the doctest environment: @@ -72,6 +63,25 @@ def ipfunc(): return 'ipfunc' +def ipos(): + """Examples that access the operating system work: + + In [1]: !echo hello + hello + + In [2]: !echo hello > /tmp/foo_iptest + + In [3]: !cat /tmp/foo_iptest + hello + + In [4]: rm -f /tmp/foo_iptest + """ + pass + + +ipos.__skip_doctest__ = os.name == "nt" + + def ranfunc(): """A function with some random output. From 9cc2f46b8734546190eff81b9d2e13ec2f810f27 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:54:04 +0300 Subject: [PATCH 1633/3726] Fix Audio doctest --- IPython/lib/display.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 19d4c35383b..33d70127a71 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -69,31 +69,33 @@ class Audio(DisplayObject): Generate a sound >>> import numpy as np - ... framerate = 44100 - ... t = np.linspace(0,5,framerate*5) - ... data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) - ... Audio(data, rate=framerate) + >>> framerate = 44100 + >>> t = np.linspace(0,5,framerate*5) + >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) + >>> Audio(data, rate=framerate) + Can also do stereo or more channels >>> dataleft = np.sin(2*np.pi*220*t) - ... dataright = np.sin(2*np.pi*224*t) - ... Audio([dataleft, dataright], rate=framerate) + >>> dataright = np.sin(2*np.pi*224*t) + >>> Audio([dataleft, dataright], rate=framerate) + From URL: - >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") - >>> Audio(url="http://www.w3schools.com/html/horse.ogg") + >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP + >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP From a File: - >>> Audio('/path/to/sound.wav') - >>> Audio(filename='/path/to/sound.ogg') + >>> Audio('/path/to/sound.wav') # doctest: +SKIP + >>> Audio(filename='/path/to/sound.ogg') # doctest: +SKIP From Bytes: - >>> Audio(b'RAW_WAV_DATA..') - >>> Audio(data=b'RAW_WAV_DATA..') + >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP + >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP See Also -------- From fda2ebeadd29608aa3e5c11f17bf836d2b1326b3 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 18:57:42 +0300 Subject: [PATCH 1634/3726] Partly revert "remove now-obsolete use of skip_doctest outside core" This partly reverts commit 02234da0d461f5ddee142d51952dc10a86b8074e. test_decorators.py is intended to test skip_doctest, the removing was wrong. --- IPython/testing/tests/test_decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/IPython/testing/tests/test_decorators.py b/IPython/testing/tests/test_decorators.py index dc1ff41d7da..801ac28086d 100644 --- a/IPython/testing/tests/test_decorators.py +++ b/IPython/testing/tests/test_decorators.py @@ -11,6 +11,7 @@ # Our own from IPython.testing import decorators as dec +from IPython.testing.skipdoctest import skip_doctest #----------------------------------------------------------------------------- # Utilities @@ -59,6 +60,7 @@ def test_deliberately_broken2(): # Verify that we can correctly skip the doctest for a function at will, but # that the docstring itself is NOT destroyed by the decorator. +@skip_doctest def doctest_bad(x,y=1,**k): """A function whose doctest we need to skip. @@ -106,6 +108,7 @@ class FooClass(object): 2 """ + @skip_doctest def __init__(self,x): """Make a FooClass. @@ -117,6 +120,7 @@ def __init__(self,x): print('Making a FooClass.') self.x = x + @skip_doctest def bar(self,y): """Example: From b5dc5d7cc67c3b418bcc23b29b5c752e416e36f8 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 19:18:32 +0300 Subject: [PATCH 1635/3726] ipexec: prevent output coloring under PyCharm QOL thing that fixes test suite fails under PyCharm --- IPython/testing/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index b4301f83af1..10bd4b6e0be 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -205,6 +205,8 @@ def ipexec(fname, options=None, commands=()): # should we keep suppressing warnings here, even after removing shims? env['PYTHONWARNINGS'] = 'ignore' # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr + # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout) + env.pop("PYCHARM_HOSTED", None) for k, v in env.items(): # Debug a bizarre failure we've seen on Windows: # TypeError: environment can only contain strings From 69dbfc1ced1c5a74380caaf2c126d6f9ad84c1f7 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 25 Oct 2021 11:02:23 -0700 Subject: [PATCH 1636/3726] reformat previous commit --- setupbase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setupbase.py b/setupbase.py index e6330ea11c5..ac2acc069f4 100644 --- a/setupbase.py +++ b/setupbase.py @@ -95,9 +95,9 @@ def find_packages(): """ excludes = ['deathrow', 'quarantine'] packages = [] - for directory, subdirs,files in os.walk('IPython'): - package = directory.replace(os.path.sep, '.') - if any(package.startswith('IPython.'+exc) for exc in excludes): + for directory, subdirs, files in os.walk("IPython"): + package = directory.replace(os.path.sep, ".") + if any(package.startswith("IPython." + exc) for exc in excludes): # package is to be excluded (e.g. deathrow) continue if '__init__.py' not in files: From 4ea65147941c1b8a6cc641bd0947ff07b728afbd Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 25 Oct 2021 21:17:21 +0300 Subject: [PATCH 1637/3726] CI: Upload coverage from Appveyor --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index fb5e59500a4..849b91aba6d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,3 +29,6 @@ install: - "%CMD_IN_ENV% cd results" test_script: - "%CMD_IN_ENV% iptest --coverage xml" +on_finish: + - pip install codecov + - codecov -e PYTHON_VERSION PYTHON_ARCH From e92f70490ff6ccfaf245eb942e8e40fe71e3cc64 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 25 Oct 2021 12:24:45 -0700 Subject: [PATCH 1638/3726] Set InterruptiblePdb as the default debugger. This make sure that user can stop pdb with SigInt, for example if you refresh your notebook while being in a debug session. --- IPython/core/interactiveshell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 37b31580d9c..dc3c726f7c2 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -43,7 +43,7 @@ from IPython.core.builtin_trap import BuiltinTrap from IPython.core.events import EventManager, available_events from IPython.core.compilerop import CachingCompiler, check_linecache_ipython -from IPython.core.debugger import Pdb +from IPython.core.debugger import InterruptiblePdb from IPython.core.display_trap import DisplayTrap from IPython.core.displayhook import DisplayHook from IPython.core.displaypub import DisplayPublisher @@ -1832,7 +1832,7 @@ def init_history(self): # Things related to exception handling and tracebacks (not debugging) #------------------------------------------------------------------------- - debugger_cls = Pdb + debugger_cls = InterruptiblePdb def init_traceback_handlers(self, custom_exceptions): # Syntax error handler. From a680045e7bcc95afaacee6d1c17e722be7b4ff4f Mon Sep 17 00:00:00 2001 From: L0uisJ0shua Date: Wed, 27 Oct 2021 00:23:16 +0800 Subject: [PATCH 1639/3726] Added Line numbers to Error Message --- IPython/core/ultratb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index c7ce562524b..29d7f1f3ed3 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -630,7 +630,7 @@ def format_record(self, frame_info): indent = ' ' * INDENT_SIZE em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) - tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, + tpl_call = 'in %s%%s%s%%s%s in Line %%s' % (Colors.vName, Colors.valEm, ColorsNormal) tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ (Colors.vName, Colors.valEm, ColorsNormal) @@ -641,14 +641,14 @@ def format_record(self, frame_info): func = frame_info.executing.code_qualname() if func == '': - call = tpl_call % (func, '') + call = tpl_call % (func, '', frame_info.lineno) else: # Decide whether to include variable details or not var_repr = eqrepr if self.include_vars else nullrepr try: call = tpl_call % (func, inspect.formatargvalues(args, varargs, varkw, - locals_, formatvalue=var_repr)) + locals_, formatvalue=var_repr), frame_info.lineno) except KeyError: # This happens in situations like errors inside generator # expressions, where local variables are listed in the From dee6e63258ffa1d731a7161b62a8e28294af8462 Mon Sep 17 00:00:00 2001 From: Julien Rabinow Date: Thu, 28 Oct 2021 01:39:42 -0700 Subject: [PATCH 1640/3726] add optional support for xdg_config|cache_dir --- IPython/paths.py | 3 +-- IPython/utils/path.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/IPython/paths.py b/IPython/paths.py index a2ea5bc1159..2dcb51949e0 100644 --- a/IPython/paths.py +++ b/IPython/paths.py @@ -54,8 +54,7 @@ def get_ipython_dir() -> str: warn(('{0} is deprecated. Move link to {1} to ' 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir))) else: - warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir))) - shutil.move(xdg_ipdir, ipdir) + ipdir = xdg_ipdir ipdir = os.path.normpath(os.path.expanduser(ipdir)) diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 273c519a004..55405cfd6cc 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -223,7 +223,7 @@ def get_xdg_dir(): env = os.environ - if os.name == 'posix' and sys.platform != 'darwin': + if os.name == "posix": # Linux, Unix, AIX, etc. # use ~/.config if empty OR not set xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') @@ -242,7 +242,7 @@ def get_xdg_cache_dir(): env = os.environ - if os.name == 'posix' and sys.platform != 'darwin': + if os.name == "posix": # Linux, Unix, AIX, etc. # use ~/.cache if empty OR not set xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') From 09e2f50debda94d4b307fe3689fd56dc30c1e2cc Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 28 Oct 2021 10:16:49 +0100 Subject: [PATCH 1641/3726] Fix quick reference documentation for 'exec' `exec` was a statement in Python 2.7, but it's a function in Python 3+. Following the old quickref line triggers a `SyntaxError` on Python 3: ``` In [2]: exec _i1 File "", line 1 exec _i1 ^ SyntaxError: Missing parentheses in call to 'exec' ``` But it is fixed with the parentheses: ``` In [3]: exec(_i1) ``` --- IPython/core/usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/core/usage.py b/IPython/core/usage.py index 37024c44567..53219bceb25 100644 --- a/IPython/core/usage.py +++ b/IPython/core/usage.py @@ -305,7 +305,7 @@ _i, _ii, _iii : Previous, next previous, next next previous input _i4, _ih[2:5] : Input history line 4, lines 2-4 -exec _i81 : Execute input history line #81 again +exec(_i81) : Execute input history line #81 again %rep 81 : Edit input history line #81 _, __, ___ : previous, next previous, next next previous output _dh : Directory history From ee3d560bb7c1a99d6b607d53607c8485a82927de Mon Sep 17 00:00:00 2001 From: Julien Rabinow Date: Thu, 28 Oct 2021 02:01:17 -0700 Subject: [PATCH 1642/3726] fix broken tests around xdg_dirs --- IPython/core/tests/test_paths.py | 16 ++++++---------- IPython/utils/tests/test_path.py | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/IPython/core/tests/test_paths.py b/IPython/core/tests/test_paths.py index e5de945e944..d1366ee34ef 100644 --- a/IPython/core/tests/test_paths.py +++ b/IPython/core/tests/test_paths.py @@ -68,7 +68,7 @@ def test_get_ipython_dir_2(): assert ipdir == os.path.join("someplace", ".ipython") def test_get_ipython_dir_3(): - """test_get_ipython_dir_3, move XDG if defined, and .ipython doesn't exist.""" + """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist.""" tmphome = TemporaryDirectory() try: with patch_get_home_dir(tmphome.name), \ @@ -80,10 +80,8 @@ def test_get_ipython_dir_3(): }), warnings.catch_warnings(record=True) as w: ipdir = paths.get_ipython_dir() - assert ipdir == os.path.join(tmphome.name, ".ipython") - if sys.platform != 'darwin': - assert len(w) == 1 - assert "Moving" in str(w[0]) + assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython") + assert len(w) == 0 finally: tmphome.cleanup() @@ -105,10 +103,8 @@ def test_get_ipython_dir_4(): }), warnings.catch_warnings(record=True) as w: ipdir = paths.get_ipython_dir() - assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython") - if sys.platform != 'darwin': - assert len(w) == 1 - assert "Ignoring" in str(w[0]) + assert len(w) == 1 + assert "Ignoring" in str(w[0]) def test_get_ipython_dir_5(): @@ -179,7 +175,7 @@ def test_get_ipython_dir_8(): def test_get_ipython_cache_dir(): with modified_env({'HOME': HOME_TEST_DIR}): - if os.name == 'posix' and sys.platform != 'darwin': + if os.name == "posix": # test default os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) with modified_env({'XDG_CACHE_HOME': None}): diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index bca72f4666e..2b17f031794 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -228,11 +228,11 @@ def test_get_xdg_dir_2(): @with_environment def test_get_xdg_dir_3(): - """test_get_xdg_dir_3, check xdg_dir not used on OS X""" + """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems""" reload(path) path.get_home_dir = lambda : HOME_TEST_DIR - os.name = "posix" - sys.platform = "darwin" + os.name = "nt" + sys.platform = "win32" env.pop('IPYTHON_DIR', None) env.pop('IPYTHONDIR', None) env.pop('XDG_CONFIG_HOME', None) From 51d01f7425742759f2e022fe903c1b68b13d4044 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 28 Oct 2021 23:28:12 +0300 Subject: [PATCH 1643/3726] TST: Restore original trace function --- IPython/core/tests/test_debugger.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index ab6b3cb0233..d3312853149 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -247,13 +247,19 @@ def raising_input(msg="", called=[0]): else: raise AssertionError("input() should only be called once!") - with patch.object(builtins, "input", raising_input): - debugger.InterruptiblePdb().set_trace() - # The way this test will fail is by set_trace() never exiting, - # resulting in a timeout by the test runner. The alternative - # implementation would involve a subprocess, but that adds issues with - # interrupting subprocesses that are rather complex, so it's simpler - # just to do it this way. + tracer_orig = sys.gettrace() + try: + with patch.object(builtins, "input", raising_input): + debugger.InterruptiblePdb().set_trace() + # The way this test will fail is by set_trace() never exiting, + # resulting in a timeout by the test runner. The alternative + # implementation would involve a subprocess, but that adds issues + # with interrupting subprocesses that are rather complex, so it's + # simpler just to do it this way. + finally: + # restore the original trace function + sys.settrace(tracer_orig) + @skip_win32 def test_xmode_skip(): From 2358ac79e4368a24506764bb8f11935a73a4899c Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 28 Oct 2021 23:28:22 +0300 Subject: [PATCH 1644/3726] CI: Collect coverage from GHA Pytest run --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0fed8344f68..8095b875843 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade -e file://$PWD#egg=ipython[test] python -m pip install --upgrade --upgrade-strategy eager trio curio - python -m pip install --upgrade pytest pytest-trio 'matplotlib!=3.2.0' + python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' python -m pip install --upgrade check-manifest pytest-cov anyio - name: Check manifest run: check-manifest @@ -45,6 +45,6 @@ jobs: cp /tmp/.coverage ./ - name: pytest run: | - pytest -v + pytest -v --cov --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From 3206fcd4dbde4b23829ad8d525933dd5f3ff50b1 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 29 Oct 2021 11:27:36 +0200 Subject: [PATCH 1645/3726] Import `reload` from `importlib` instead of `imp` The `imp` module was deprecated in Python 3.4 in favor of `importlib`, and the function `imp.reload` is now just a wrapper for `importlib.reload`. --- IPython/extensions/autoreload.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index bcf1a198021..0e43ec61fe5 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -121,9 +121,8 @@ import types import weakref import gc -from importlib import import_module +from importlib import import_module, reload from importlib.util import source_from_cache -from imp import reload # ------------------------------------------------------------------------------ # Autoreload functionality From 74c6e32237d1aca850fe30076f36c3bda1f38757 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 29 Oct 2021 08:03:47 -0700 Subject: [PATCH 1646/3726] Disable codecov for current patch It is too sensitive and fails when the number of lines in a file decreases. --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 8015ac1d6fc..d65a8d9cfdf 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,6 @@ coverage: status: + patch: off project: default: target: auto From fa30063d3e1efa917907e665d76e1fd51a528ce0 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 29 Oct 2021 18:39:46 +0300 Subject: [PATCH 1647/3726] Make knownfailureif Pytest-compatible --- IPython/external/decorators/_decorators.py | 9 ++++++++- .../decorators/_numpy_testing_noseclasses.py | 12 +++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/IPython/external/decorators/_decorators.py b/IPython/external/decorators/_decorators.py index 18f847adadd..2dcf621abd9 100644 --- a/IPython/external/decorators/_decorators.py +++ b/IPython/external/decorators/_decorators.py @@ -133,9 +133,16 @@ def knownfail_decorator(f): # import time overhead at actual test-time. import nose + try: + from pytest import xfail + except ImportError: + + def xfail(): + raise KnownFailureTest(msg) + def knownfailer(*args, **kwargs): if fail_condition: - raise KnownFailureTest(msg) + xfail(msg) else: return f(*args, **kwargs) return nose.tools.make_decorator(f)(knownfailer) diff --git a/IPython/external/decorators/_numpy_testing_noseclasses.py b/IPython/external/decorators/_numpy_testing_noseclasses.py index ca6ccd87bbc..7a4360cf5b6 100644 --- a/IPython/external/decorators/_numpy_testing_noseclasses.py +++ b/IPython/external/decorators/_numpy_testing_noseclasses.py @@ -7,9 +7,15 @@ from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin -class KnownFailureTest(Exception): - '''Raise this exception to mark a test as a known failing test.''' - pass + +try: + import pytest + + KnownFailureTest = pytest.xfail.Exception +except ImportError: + + class KnownFailureTest(Exception): + """Raise this exception to mark a test as a known failing test.""" class KnownFailure(ErrorClassPlugin): From 6572ff13316cca1274dbf8ae366fd8f50dc4d362 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 29 Oct 2021 20:46:24 +0300 Subject: [PATCH 1648/3726] Fix onlyif_cmds_exist skip message --- IPython/testing/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index a2458ce13fa..3ea101ce489 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -376,7 +376,7 @@ def onlyif_cmds_exist(*commands): Decorator to skip test when at least one of `commands` is not found. """ for cmd in commands: - reason = "This test runs only if command '{cmd}' is installed" + reason = f"This test runs only if command '{cmd}' is installed" if not shutil.which(cmd): if os.environ.get("IPTEST_WORKING_DIR", None) is not None: return skip(reason) From 595b4f4e700660dcdc32bb2622a4f8775f471039 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 29 Oct 2021 20:50:06 +0300 Subject: [PATCH 1649/3726] CI: Remove noop %CMD_IN_ENV% usage --- appveyor.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 849b91aba6d..d31e199868b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,13 +22,13 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - "%CMD_IN_ENV% python -m pip install --upgrade setuptools pip" - - "%CMD_IN_ENV% pip install nose coverage pytest" - - "%CMD_IN_ENV% pip install .[test]" - - "%CMD_IN_ENV% mkdir results" - - "%CMD_IN_ENV% cd results" + - python -m pip install --upgrade setuptools pip + - pip install nose coverage pytest + - pip install .[test] + - mkdir results + - cd results test_script: - - "%CMD_IN_ENV% iptest --coverage xml" + - iptest --coverage xml on_finish: - pip install codecov - codecov -e PYTHON_VERSION PYTHON_ARCH From e00c3e75765a679860a5e6ea7f8f9b0a296a4cc1 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 29 Oct 2021 21:43:36 +0300 Subject: [PATCH 1650/3726] CI: Switch to the new codecov uploader Apparently the python one is deprecated already --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d31e199868b..eda8a2d1436 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,5 +30,5 @@ install: test_script: - iptest --coverage xml on_finish: - - pip install codecov - - codecov -e PYTHON_VERSION PYTHON_ARCH + - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe + - codecov -e PYTHON_VERSION,PYTHON_ARCH From 56e3f69aa650fdf6df8f0ff3b22eaa967dd6f73e Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 29 Oct 2021 21:43:50 +0300 Subject: [PATCH 1651/3726] CI: Run Pytest tests on Appveyor --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index eda8a2d1436..514da8ed3a9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,12 +23,14 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - - pip install nose coverage pytest + - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib - pip install .[test] - mkdir results - cd results test_script: - iptest --coverage xml + - cd .. + - pytest -ra --cov --cov-report=xml on_finish: - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe - codecov -e PYTHON_VERSION,PYTHON_ARCH From bc86e0ca39f683c3cb5df81cb2b2eb94211ccd52 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 29 Oct 2021 14:52:13 -0700 Subject: [PATCH 1652/3726] WN 7.29 --- docs/source/whatsnew/version7.rst | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 990e0eb7021..02e1dc43d3a 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,47 @@ 7.x Series ============ +.. _version 7.29: + +IPython 7.29 +============ + + +IPython 7.29 brings a couple of new functionalities to IPython and a number of bugfixes. +It is one of the largest recent release, relatively speaking, with close to 15 Pull Requests. + + +- fix an issue where base64 was returned instead of bytes when showing figures :ghpull:`13162` +- fix compatibility with PyQt6, PySide 6 :ghpull:`13172`. This may be of + interest if you are running on Apple Silicon as only qt6.2+ is natively + compatible. +- fix matplotlib qtagg eventloop :ghpull:`13179` +- Multiple docs fixes, typos, ... etc. +- Debugger will now exit by default on SigInt :ghpull:`13218`, this will be + useful in notebook/lab if you forgot to exit the debugger. "Interrupt Kernel" + will now exist the debugger. + +It give Pdb the ability to skip code in decorators. If functions contain a +special value names ``__debuggerskip__ = True|False``, the function will not be +stepped into, and Pdb will step into lower frames only if the value is set to +``False``. The exact behavior is still likely to have corner cases and will be +refined in subsequent releases. Feedback welcome. See the debugger module +documentation for more info. Thanks to the `D. E. Shaw +group `__ for funding this feature. + +The main branch of IPython is receiving a number of changes as we received a +`NumFOCUS SDG `__ +($4800), to help us finish replacing ``nose`` by ``pytest``, and make IPython +future proof with an 8.0 release. + + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + .. _version 7.28: From 302ac24f2c26e43ab260028066b53fd53bf40f21 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 29 Oct 2021 16:43:08 -0700 Subject: [PATCH 1653/3726] Fix link and try to fix list. Though it seem like the css is broken for lists on RTD --- docs/source/whatsnew/version7.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 02e1dc43d3a..42e12e52543 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -12,15 +12,15 @@ IPython 7.29 brings a couple of new functionalities to IPython and a number of b It is one of the largest recent release, relatively speaking, with close to 15 Pull Requests. -- fix an issue where base64 was returned instead of bytes when showing figures :ghpull:`13162` -- fix compatibility with PyQt6, PySide 6 :ghpull:`13172`. This may be of - interest if you are running on Apple Silicon as only qt6.2+ is natively - compatible. -- fix matplotlib qtagg eventloop :ghpull:`13179` -- Multiple docs fixes, typos, ... etc. -- Debugger will now exit by default on SigInt :ghpull:`13218`, this will be - useful in notebook/lab if you forgot to exit the debugger. "Interrupt Kernel" - will now exist the debugger. + - fix an issue where base64 was returned instead of bytes when showing figures :ghpull:`13162` + - fix compatibility with PyQt6, PySide 6 :ghpull:`13172`. This may be of + interest if you are running on Apple Silicon as only qt6.2+ is natively + compatible. + - fix matplotlib qtagg eventloop :ghpull:`13179` + - Multiple docs fixes, typos, ... etc. + - Debugger will now exit by default on SigInt :ghpull:`13218`, this will be + useful in notebook/lab if you forgot to exit the debugger. "Interrupt Kernel" + will now exist the debugger. It give Pdb the ability to skip code in decorators. If functions contain a special value names ``__debuggerskip__ = True|False``, the function will not be @@ -38,7 +38,7 @@ future proof with an 8.0 release. Many thanks to all the contributors to this release. You can find all individual contributions to this milestone `on github -`__. +`__. Thanks as well to the `D. E. Shaw group `__ for sponsoring work on IPython and related libraries. From 7c02362387842d9cdf8000ec2abecdec56687fb5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 29 Oct 2021 17:02:52 -0700 Subject: [PATCH 1654/3726] Update what's of of older IPython (forgotten) --- .../enable-to-add-extra-attrs-to-iframe.rst | 38 ------------- .../whatsnew/pr/pastebin-expiry-days.rst | 7 --- docs/source/whatsnew/version7.rst | 53 +++++++++++++++++++ 3 files changed, 53 insertions(+), 45 deletions(-) delete mode 100644 docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst delete mode 100644 docs/source/whatsnew/pr/pastebin-expiry-days.rst diff --git a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst b/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst deleted file mode 100644 index 1954bc439b8..00000000000 --- a/docs/source/whatsnew/pr/enable-to-add-extra-attrs-to-iframe.rst +++ /dev/null @@ -1,38 +0,0 @@ -``YouTubeVideo`` autoplay and the ability to add extra attributes to ``IFrame`` -=============================================================================== - -You can add any extra attributes to the `` - -Related to the above, the ``YouTubeVideo`` class now takes an -``allow_autoplay`` flag, which sets up the iframe of the embedded YouTube video -such that it allows autoplay. - -.. note:: - Whether this works depends on the autoplay policy of the browser rendering - the HTML allowing it. It also could get blocked by some browser extensions. - -Try it out! -:: - - In [1]: from IPython.display import YouTubeVideo - - In [2]: YouTubeVideo("dQw4w9WgXcQ", allow_autoplay=True) - -🙃 diff --git a/docs/source/whatsnew/pr/pastebin-expiry-days.rst b/docs/source/whatsnew/pr/pastebin-expiry-days.rst deleted file mode 100644 index 68faa7851f8..00000000000 --- a/docs/source/whatsnew/pr/pastebin-expiry-days.rst +++ /dev/null @@ -1,7 +0,0 @@ -Pastebin magic expiry days option -================================= - -The Pastebin magic now has ``-e`` option to determine -the number of days for paste expiration. For example -the paste that created with ``%pastebin -e 20 1`` magic will -be available for next 20 days. diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 42e12e52543..20fa0397220 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -80,6 +80,49 @@ New Features: - Reword the YouTubeVideo autoplay WN :ghpull:`13147` +Highlighted features +-------------------- + + +``YouTubeVideo`` autoplay and the ability to add extra attributes to ``IFrame`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can add any extra attributes to the `` + +Related to the above, the ``YouTubeVideo`` class now takes an +``allow_autoplay`` flag, which sets up the iframe of the embedded YouTube video +such that it allows autoplay. + +.. note:: + Whether this works depends on the autoplay policy of the browser rendering + the HTML allowing it. It also could get blocked by some browser extensions. + +Try it out! +:: + + In [1]: from IPython.display import YouTubeVideo + + In [2]: YouTubeVideo("dQw4w9WgXcQ", allow_autoplay=True) + + + Thanks ------ @@ -137,6 +180,16 @@ Of particular interest are the following Pull-requests: :ghpull:`13056` - Make Ipython.utils.timing work with jupyterlite :ghpull:`13050`. +Pastebin magic expiry days option +--------------------------------- + +The Pastebin magic now has ``-e`` option to determine +the number of days for paste expiration. For example +the paste that created with ``%pastebin -e 20 1`` magic will +be available for next 20 days. + + + Thanks From f2a1048624a82a98cf4540cbf471e141840f4a6a Mon Sep 17 00:00:00 2001 From: Arthur Moreira Date: Fri, 29 Oct 2021 21:28:22 -0300 Subject: [PATCH 1655/3726] Add line number to error messages --- IPython/core/ultratb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index c7ce562524b..8bc7105c321 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -630,10 +630,10 @@ def format_record(self, frame_info): indent = ' ' * INDENT_SIZE em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) - tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, + tpl_call = 'line %s, in %s%%s%s%%s%s' % (frame_info.lineno, Colors.vName, Colors.valEm, ColorsNormal) - tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ - (Colors.vName, Colors.valEm, ColorsNormal) + tpl_call_fail = 'line %s, in %s%%s%s(***failed resolving arguments***)%s' % \ + (frame_info.lineno, Colors.vName, Colors.valEm, ColorsNormal) tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) link = _format_filename(frame_info.filename, Colors.filenameEm, ColorsNormal) From c177aef56af7c68a3ea49e20b5ec90af2277b1a4 Mon Sep 17 00:00:00 2001 From: Arthur Moreira Date: Fri, 29 Oct 2021 21:52:29 -0300 Subject: [PATCH 1656/3726] Running darker to format changes --- IPython/core/ultratb.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 8bc7105c321..cf836a25908 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -628,13 +628,21 @@ def format_record(self, frame_info): return ' %s[... skipping similar frames: %s]%s\n' % ( Colors.excName, frame_info.description, ColorsNormal) - indent = ' ' * INDENT_SIZE - em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) - tpl_call = 'line %s, in %s%%s%s%%s%s' % (frame_info.lineno, Colors.vName, Colors.valEm, - ColorsNormal) - tpl_call_fail = 'line %s, in %s%%s%s(***failed resolving arguments***)%s' % \ - (frame_info.lineno, Colors.vName, Colors.valEm, ColorsNormal) - tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) + indent = " " * INDENT_SIZE + em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal) + tpl_call = "line %s, in %s%%s%s%%s%s" % ( + frame_info.lineno, + Colors.vName, + Colors.valEm, + ColorsNormal, + ) + tpl_call_fail = "line %s, in %s%%s%s(***failed resolving arguments***)%s" % ( + frame_info.lineno, + Colors.vName, + Colors.valEm, + ColorsNormal, + ) + tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal) link = _format_filename(frame_info.filename, Colors.filenameEm, ColorsNormal) args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) From 92296e1c40367166d8b99b0d4e383920c5e02de9 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 31 Oct 2021 17:38:49 +0300 Subject: [PATCH 1657/3726] test_completer tests resurrection * Class in class tests never was a thing. * Generating methods seems to not work even in nose, they report no coverage. * Installed pandas in CI to run test_dataframe_key_completion --- .github/workflows/test.yml | 2 +- IPython/core/tests/test_completer.py | 64 ++++++++++++++++------------ appveyor.yml | 2 +- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8095b875843..0704f376b94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade -e file://$PWD#egg=ipython[test] python -m pip install --upgrade --upgrade-strategy eager trio curio - python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' + python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' pandas python -m pip install --upgrade check-manifest pytest-cov anyio - name: Check manifest run: check-manifest diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 319bc2da08e..b39818dc80d 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -328,20 +328,18 @@ def test_no_ascii_back_completion(self): name, matches = ip.complete("\\" + letter) self.assertEqual(matches, []) - class CompletionSplitterTestCase(unittest.TestCase): - def setUp(self): - self.sp = completer.CompletionSplitter() - - def test_delim_setting(self): - self.sp.delims = " " - self.assertEqual(self.sp.delims, " ") - self.assertEqual(self.sp._delim_expr, r"[\ ]") - - def test_spaces(self): - """Test with only spaces as split chars.""" - self.sp.delims = " " - t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")] - check_line_split(self.sp, t) + def test_delim_setting(self): + sp = completer.CompletionSplitter() + sp.delims = " " + self.assertEqual(sp.delims, " ") + self.assertEqual(sp._delim_expr, r"[\ ]") + + def test_spaces(self): + """Test with only spaces as split chars.""" + sp = completer.CompletionSplitter() + sp.delims = " " + t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")] + check_line_split(sp, t) def test_has_open_quotes1(self): for s in ["'", "'''", "'hi' '"]: @@ -462,7 +460,7 @@ def _test_complete(reason, s, comp, start=None, end=None): ip.Completer.use_jedi = True completions = set(ip.Completer.completions(s, l)) ip.Completer.use_jedi = False - assert_in(Completion(start, end, comp), completions, reason) + assert Completion(start, end, comp) in completions, reason def _test_not_complete(reason, s, comp): l = len(s) @@ -470,18 +468,18 @@ def _test_not_complete(reason, s, comp): ip.Completer.use_jedi = True completions = set(ip.Completer.completions(s, l)) ip.Completer.use_jedi = False - assert_not_in(Completion(l, l, comp), completions, reason) + assert Completion(l, l, comp) not in completions, reason import jedi jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3]) if jedi_version > (0, 10): - yield _test_complete, "jedi >0.9 should complete and not crash", "a=1;a.", "real" - yield _test_complete, "can infer first argument", 'a=(1,"foo");a[0].', "real" - yield _test_complete, "can infer second argument", 'a=(1,"foo");a[1].', "capitalize" - yield _test_complete, "cover duplicate completions", "im", "import", 0, 2 + _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real") + _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real") + _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize") + _test_complete("cover duplicate completions", "im", "import", 0, 2) - yield _test_not_complete, "does not mix types", 'a=(1,"foo");a[0].', "capitalize" + _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize") def test_completion_have_signature(self): """ @@ -548,15 +546,27 @@ def _(line, cursor_pos, expect, message, completion): self.assertIn(completion, completions) with provisionalcompleter(): - yield _, "a[0].", 5, "a[0].real", "Should have completed on a[0].: %s", Completion( - 5, 5, "real" + _( + "a[0].", + 5, + "a[0].real", + "Should have completed on a[0].: %s", + Completion(5, 5, "real"), ) - yield _, "a[0].r", 6, "a[0].real", "Should have completed on a[0].r: %s", Completion( - 5, 6, "real" + _( + "a[0].r", + 6, + "a[0].real", + "Should have completed on a[0].r: %s", + Completion(5, 6, "real"), ) - yield _, "a[0].from_", 10, "a[0].from_bytes", "Should have completed on a[0].from_: %s", Completion( - 5, 10, "from_bytes" + _( + "a[0].from_", + 10, + "a[0].from_bytes", + "Should have completed on a[0].from_: %s", + Completion(5, 10, "from_bytes"), ) def test_omit__names(self): diff --git a/appveyor.yml b/appveyor.yml index 514da8ed3a9..031ba7f6973 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib + - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib pandas - pip install .[test] - mkdir results - cd results From ca9ff08faab633cfca5c74b3b70cd7ce44e3d399 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 31 Oct 2021 00:37:03 +0300 Subject: [PATCH 1658/3726] Fix xfail polyfill signature --- IPython/external/decorators/_decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/external/decorators/_decorators.py b/IPython/external/decorators/_decorators.py index 2dcf621abd9..308652febcc 100644 --- a/IPython/external/decorators/_decorators.py +++ b/IPython/external/decorators/_decorators.py @@ -137,7 +137,7 @@ def knownfail_decorator(f): from pytest import xfail except ImportError: - def xfail(): + def xfail(msg): raise KnownFailureTest(msg) def knownfailer(*args, **kwargs): From b2d195e33ddbd38ca5de2c360345315406c266ce Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 31 Oct 2021 00:38:14 +0300 Subject: [PATCH 1659/3726] CI: Colorize and widen Pytest summary Pytest shows some error messages only in summary while also truncating lines there. --- .github/workflows/test.yml | 4 +++- appveyor.yml | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0704f376b94..1a99ac62426 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,9 @@ jobs: cp /tmp/ipy_coverage.xml ./ cp /tmp/.coverage ./ - name: pytest + env: + COLUMNS: 120 run: | - pytest -v --cov --cov-report=xml + pytest --color=yes -v --cov --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/appveyor.yml b/appveyor.yml index 031ba7f6973..26df940d77e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,6 +3,9 @@ matrix: fast_finish: true # immediately finish build once one of the jobs fails. environment: + global: + COLUMNS: 120 # Appveyor web viwer window width is 130 chars + matrix: - PYTHON: "C:\\Python37-x64" @@ -30,7 +33,7 @@ install: test_script: - iptest --coverage xml - cd .. - - pytest -ra --cov --cov-report=xml + - pytest --color=yes -ra --cov --cov-report=xml on_finish: - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe - codecov -e PYTHON_VERSION,PYTHON_ARCH From 8a04efadf7e4cd7ced49f78a169de8c4cb2db75a Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 20:10:41 +0300 Subject: [PATCH 1660/3726] Remove unused _find_cmd It was replaced with shutil/py3compat.which in f42c9edb152ac392caa39fe9e50373ef0ec42a55 --- IPython/utils/_process_cli.py | 8 -------- IPython/utils/_process_posix.py | 8 -------- IPython/utils/_process_win32.py | 21 --------------------- 3 files changed, 37 deletions(-) diff --git a/IPython/utils/_process_cli.py b/IPython/utils/_process_cli.py index 89a31c31643..03b2ecc7997 100644 --- a/IPython/utils/_process_cli.py +++ b/IPython/utils/_process_cli.py @@ -22,14 +22,6 @@ from IPython.utils import py3compat from ._process_common import arg_split -def _find_cmd(cmd): - """Find the full path to a command using which.""" - paths = System.Environment.GetEnvironmentVariable("PATH").Split(os.pathsep) - for path in paths: - filename = os.path.join(path, cmd) - if System.IO.File.Exists(filename): - return py3compat.decode(filename) - raise OSError("command %r not found" % cmd) def system(cmd): """ diff --git a/IPython/utils/_process_posix.py b/IPython/utils/_process_posix.py index a9b0e2182c0..5f6b1ab200f 100644 --- a/IPython/utils/_process_posix.py +++ b/IPython/utils/_process_posix.py @@ -31,14 +31,6 @@ # Function definitions #----------------------------------------------------------------------------- -def _find_cmd(cmd): - """Find the full path to a command using which.""" - - path = sp.Popen(['/usr/bin/env', 'which', cmd], - stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0] - return py3compat.decode(path) - - class ProcessHandler(object): """Execute subprocesses under the control of pexpect. """ diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py index 2f072a8779e..36fb092d7b2 100644 --- a/IPython/utils/_process_win32.py +++ b/IPython/utils/_process_win32.py @@ -71,27 +71,6 @@ def __exit__(self, exc_type, exc_value, traceback): os.chdir(self.path) -def _find_cmd(cmd): - """Find the full path to a .bat or .exe using the win32api module.""" - try: - from win32api import SearchPath - except ImportError as e: - raise ImportError('you need to have pywin32 installed for this to work') from e - else: - PATH = os.environ['PATH'] - extensions = ['.exe', '.com', '.bat', '.py'] - path = None - for ext in extensions: - try: - path = SearchPath(PATH, cmd, ext)[0] - except: - pass - if path is None: - raise OSError("command %r not found" % cmd) - else: - return path - - def _system_body(p): """Callback for _system.""" enc = DEFAULT_ENCODING From bb11d0f597a32eab43987a2eb5b391c308f3ae7f Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 2 Nov 2021 19:16:25 +0300 Subject: [PATCH 1661/3726] Pytest ipdoctest plugin base This is a copy of Pytest doctest.py without any changes. --- IPython/testing/plugin/pytest_ipdoctest.py | 724 +++++++++++++++++++++ 1 file changed, 724 insertions(+) create mode 100644 IPython/testing/plugin/pytest_ipdoctest.py diff --git a/IPython/testing/plugin/pytest_ipdoctest.py b/IPython/testing/plugin/pytest_ipdoctest.py new file mode 100644 index 00000000000..64e8f0e0eee --- /dev/null +++ b/IPython/testing/plugin/pytest_ipdoctest.py @@ -0,0 +1,724 @@ +"""Discover and run doctests in modules and test files.""" +import bdb +import inspect +import platform +import sys +import traceback +import types +import warnings +from contextlib import contextmanager +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +import py.path + +import pytest +from _pytest import outcomes +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import ReprFileLocation +from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.compat import safe_getattr +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest +from _pytest.nodes import Collector +from _pytest.outcomes import OutcomeException +from _pytest.pathlib import import_path +from _pytest.python_api import approx +from _pytest.warning_types import PytestWarning + +if TYPE_CHECKING: + import doctest + +DOCTEST_REPORT_CHOICE_NONE = "none" +DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" +DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" +DOCTEST_REPORT_CHOICE_UDIFF = "udiff" +DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" + +DOCTEST_REPORT_CHOICES = ( + DOCTEST_REPORT_CHOICE_NONE, + DOCTEST_REPORT_CHOICE_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF, + DOCTEST_REPORT_CHOICE_UDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, +) + +# Lazy definition of runner class +RUNNER_CLASS = None +# Lazy definition of output checker class +CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "doctest_optionflags", + "option flags for doctests", + type="args", + default=["ELLIPSIS"], + ) + parser.addini( + "doctest_encoding", "encoding used for doctest files", default="utf-8" + ) + group = parser.getgroup("collect") + group.addoption( + "--doctest-modules", + action="store_true", + default=False, + help="run doctests in all .py modules", + dest="doctestmodules", + ) + group.addoption( + "--doctest-report", + type=str.lower, + default="udiff", + help="choose another output format for diffs on doctest failure", + choices=DOCTEST_REPORT_CHOICES, + dest="doctestreport", + ) + group.addoption( + "--doctest-glob", + action="append", + default=[], + metavar="pat", + help="doctests file matching pattern, default: test*.txt", + dest="doctestglob", + ) + group.addoption( + "--doctest-ignore-import-errors", + action="store_true", + default=False, + help="ignore doctest ImportErrors", + dest="doctest_ignore_import_errors", + ) + group.addoption( + "--doctest-continue-on-failure", + action="store_true", + default=False, + help="for a given doctest, continue to run after the first failure", + dest="doctest_continue_on_failure", + ) + + +def pytest_unconfigure() -> None: + global RUNNER_CLASS + + RUNNER_CLASS = None + + +def pytest_collect_file( + path: py.path.local, parent: Collector, +) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: + config = parent.config + if path.ext == ".py": + if config.option.doctestmodules and not _is_setup_py(path): + mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) + return mod + elif _is_doctest(config, path, parent): + txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) + return txt + return None + + +def _is_setup_py(path: py.path.local) -> bool: + if path.basename != "setup.py": + return False + contents = path.read_binary() + return b"setuptools" in contents or b"distutils" in contents + + +def _is_doctest(config: Config, path: py.path.local, parent) -> bool: + if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): + return True + globs = config.getoption("doctestglob") or ["test*.txt"] + for glob in globs: + if path.check(fnmatch=glob): + return True + return False + + +class ReprFailDoctest(TerminalRepr): + def __init__( + self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + ) -> None: + self.reprlocation_lines = reprlocation_lines + + def toterminal(self, tw: TerminalWriter) -> None: + for reprlocation, lines in self.reprlocation_lines: + for line in lines: + tw.line(line) + reprlocation.toterminal(tw) + + +class MultipleDoctestFailures(Exception): + def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + super().__init__() + self.failures = failures + + +def _init_runner_class() -> Type["doctest.DocTestRunner"]: + import doctest + + class PytestDoctestRunner(doctest.DebugRunner): + """Runner to collect failures. + + Note that the out variable in this case is a list instead of a + stdout-like object. + """ + + def __init__( + self, + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, + ) -> None: + doctest.DebugRunner.__init__( + self, checker=checker, verbose=verbose, optionflags=optionflags + ) + self.continue_on_failure = continue_on_failure + + def report_failure( + self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, + ) -> None: + failure = doctest.DocTestFailure(test, example, got) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + def report_unexpected_exception( + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + ) -> None: + if isinstance(exc_info[1], OutcomeException): + raise exc_info[1] + if isinstance(exc_info[1], bdb.BdbQuit): + outcomes.exit("Quitting debugger") + failure = doctest.UnexpectedException(test, example, exc_info) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + return PytestDoctestRunner + + +def _get_runner( + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, +) -> "doctest.DocTestRunner": + # We need this in order to do a lazy import on doctest + global RUNNER_CLASS + if RUNNER_CLASS is None: + RUNNER_CLASS = _init_runner_class() + # Type ignored because the continue_on_failure argument is only defined on + # PytestDoctestRunner, which is lazily defined so can't be used as a type. + return RUNNER_CLASS( # type: ignore + checker=checker, + verbose=verbose, + optionflags=optionflags, + continue_on_failure=continue_on_failure, + ) + + +class DoctestItem(pytest.Item): + def __init__( + self, + name: str, + parent: "Union[DoctestTextfile, DoctestModule]", + runner: Optional["doctest.DocTestRunner"] = None, + dtest: Optional["doctest.DocTest"] = None, + ) -> None: + super().__init__(name, parent) + self.runner = runner + self.dtest = dtest + self.obj = None + self.fixture_request: Optional[FixtureRequest] = None + + @classmethod + def from_parent( # type: ignore + cls, + parent: "Union[DoctestTextfile, DoctestModule]", + *, + name: str, + runner: "doctest.DocTestRunner", + dtest: "doctest.DocTest", + ): + # incompatible signature due to to imposed limits on sublcass + """The public named constructor.""" + return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) + + def setup(self) -> None: + if self.dtest is not None: + self.fixture_request = _setup_fixtures(self) + globs = dict(getfixture=self.fixture_request.getfixturevalue) + for name, value in self.fixture_request.getfixturevalue( + "doctest_namespace" + ).items(): + globs[name] = value + self.dtest.globs.update(globs) + + def runtest(self) -> None: + assert self.dtest is not None + assert self.runner is not None + _check_all_skipped(self.dtest) + self._disable_output_capturing_for_darwin() + failures: List["doctest.DocTestFailure"] = [] + # Type ignored because we change the type of `out` from what + # doctest expects. + self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] + if failures: + raise MultipleDoctestFailures(failures) + + def _disable_output_capturing_for_darwin(self) -> None: + """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" + if platform.system() != "Darwin": + return + capman = self.config.pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend_global_capture(in_=True) + out, err = capman.read_global_capture() + sys.stdout.write(out) + sys.stderr.write(err) + + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException], + ) -> Union[str, TerminalRepr]: + import doctest + + failures: Optional[ + Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] + ] = (None) + if isinstance( + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + ): + failures = [excinfo.value] + elif isinstance(excinfo.value, MultipleDoctestFailures): + failures = excinfo.value.failures + + if failures is not None: + reprlocation_lines = [] + for failure in failures: + example = failure.example + test = failure.test + filename = test.filename + if test.lineno is None: + lineno = None + else: + lineno = test.lineno + example.lineno + 1 + message = type(failure).__name__ + # TODO: ReprFileLocation doesn't expect a None lineno. + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] + checker = _get_checker() + report_choice = _get_report_choice( + self.config.getoption("doctestreport") + ) + if lineno is not None: + assert failure.test.docstring is not None + lines = failure.test.docstring.splitlines(False) + # add line numbers to the left of the error message + assert test.lineno is not None + lines = [ + "%03d %s" % (i + test.lineno + 1, x) + for (i, x) in enumerate(lines) + ] + # trim docstring error lines to 10 + lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] + else: + lines = [ + "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" + ] + indent = ">>>" + for line in example.source.splitlines(): + lines.append(f"??? {indent} {line}") + indent = "..." + if isinstance(failure, doctest.DocTestFailure): + lines += checker.output_difference( + example, failure.got, report_choice + ).split("\n") + else: + inner_excinfo = ExceptionInfo(failure.exc_info) + lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] + lines += [ + x.strip("\n") + for x in traceback.format_exception(*failure.exc_info) + ] + reprlocation_lines.append((reprlocation, lines)) + return ReprFailDoctest(reprlocation_lines) + else: + return super().repr_failure(excinfo) + + def reportinfo(self): + assert self.dtest is not None + return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name + + +def _get_flag_lookup() -> Dict[str, int]: + import doctest + + return dict( + DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, + DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, + NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, + ELLIPSIS=doctest.ELLIPSIS, + IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, + COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, + ALLOW_UNICODE=_get_allow_unicode_flag(), + ALLOW_BYTES=_get_allow_bytes_flag(), + NUMBER=_get_number_flag(), + ) + + +def get_optionflags(parent): + optionflags_str = parent.config.getini("doctest_optionflags") + flag_lookup_table = _get_flag_lookup() + flag_acc = 0 + for flag in optionflags_str: + flag_acc |= flag_lookup_table[flag] + return flag_acc + + +def _get_continue_on_failure(config): + continue_on_failure = config.getvalue("doctest_continue_on_failure") + if continue_on_failure: + # We need to turn off this if we use pdb since we should stop at + # the first failure. + if config.getvalue("usepdb"): + continue_on_failure = False + return continue_on_failure + + +class DoctestTextfile(pytest.Module): + obj = None + + def collect(self) -> Iterable[DoctestItem]: + import doctest + + # Inspired by doctest.testfile; ideally we would use it directly, + # but it doesn't support passing a custom checker. + encoding = self.config.getini("doctest_encoding") + text = self.fspath.read_text(encoding) + filename = str(self.fspath) + name = self.fspath.basename + globs = {"__name__": "__main__"} + + optionflags = get_optionflags(self) + + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + parser = doctest.DocTestParser() + test = parser.get_doctest(text, globs, name, filename, 0) + if test.examples: + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + +def _check_all_skipped(test: "doctest.DocTest") -> None: + """Raise pytest.skip() if all examples in the given DocTest have the SKIP + option set.""" + import doctest + + all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) + if all_skipped: + pytest.skip("all tests skipped by +SKIP option") + + +def _is_mocked(obj: object) -> bool: + """Return if an object is possibly a mock object by checking the + existence of a highly improbable attribute.""" + return ( + safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) + is not None + ) + + +@contextmanager +def _patch_unwrap_mock_aware() -> Generator[None, None, None]: + """Context manager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse into them.""" + real_unwrap = inspect.unwrap + + def _mock_aware_unwrap( + func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + ) -> Any: + try: + if stop is None or stop is _is_mocked: + return real_unwrap(func, stop=_is_mocked) + _stop = stop + return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) + except Exception as e: + warnings.warn( + "Got %r when unwrapping %r. This is usually caused " + "by a violation of Python's object protocol; see e.g. " + "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), + PytestWarning, + ) + raise + + inspect.unwrap = _mock_aware_unwrap + try: + yield + finally: + inspect.unwrap = real_unwrap + + +class DoctestModule(pytest.Module): + def collect(self) -> Iterable[DoctestItem]: + import doctest + + class MockAwareDocTestFinder(doctest.DocTestFinder): + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. + + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ + + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. + + https://bugs.python.org/issue17446 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + # Type ignored because this is a private function. + return doctest.DocTestFinder._find_lineno( # type: ignore + self, obj, source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): + + # Type ignored because this is a private function. + doctest.DocTestFinder._find( # type: ignore + self, tests, obj, name, module, source_lines, globs, seen + ) + + if self.fspath.basename == "conftest.py": + module = self.config.pluginmanager._importconftest( + self.fspath, self.config.getoption("importmode") + ) + else: + try: + module = import_path(self.fspath) + except ImportError: + if self.config.getvalue("doctest_ignore_import_errors"): + pytest.skip("unable to import module %r" % self.fspath) + else: + raise + # Uses internal doctest module parsing mechanism. + finder = MockAwareDocTestFinder() + optionflags = get_optionflags(self) + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + for test in finder.find(module, module.__name__): + if test.examples: # skip empty doctests + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + +def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: + """Used by DoctestTextfile and DoctestItem to setup fixture information.""" + + def func() -> None: + pass + + doctest_item.funcargs = {} # type: ignore[attr-defined] + fm = doctest_item.session._fixturemanager + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] + node=doctest_item, func=func, cls=None, funcargs=False + ) + fixture_request = FixtureRequest(doctest_item, _ispytest=True) + fixture_request._fillfixtures() + return fixture_request + + +def _init_checker_class() -> Type["doctest.OutputChecker"]: + import doctest + import re + + class LiteralsOutputChecker(doctest.OutputChecker): + # Based on doctest_nose_plugin.py from the nltk project + # (https://github.com/nltk/nltk) and on the "numtest" doctest extension + # by Sebastien Boisgerault (https://github.com/boisgera/numtest). + + _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) + _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) + _number_re = re.compile( + r""" + (?P + (?P + (?P [+-]?\d*)\.(?P\d+) + | + (?P [+-]?\d+)\. + ) + (?: + [Ee] + (?P [+-]?\d+) + )? + | + (?P [+-]?\d+) + (?: + [Ee] + (?P [+-]?\d+) + ) + ) + """, + re.VERBOSE, + ) + + def check_output(self, want: str, got: str, optionflags: int) -> bool: + if doctest.OutputChecker.check_output(self, want, got, optionflags): + return True + + allow_unicode = optionflags & _get_allow_unicode_flag() + allow_bytes = optionflags & _get_allow_bytes_flag() + allow_number = optionflags & _get_number_flag() + + if not allow_unicode and not allow_bytes and not allow_number: + return False + + def remove_prefixes(regex: Pattern[str], txt: str) -> str: + return re.sub(regex, r"\1\2", txt) + + if allow_unicode: + want = remove_prefixes(self._unicode_literal_re, want) + got = remove_prefixes(self._unicode_literal_re, got) + + if allow_bytes: + want = remove_prefixes(self._bytes_literal_re, want) + got = remove_prefixes(self._bytes_literal_re, got) + + if allow_number: + got = self._remove_unwanted_precision(want, got) + + return doctest.OutputChecker.check_output(self, want, got, optionflags) + + def _remove_unwanted_precision(self, want: str, got: str) -> str: + wants = list(self._number_re.finditer(want)) + gots = list(self._number_re.finditer(got)) + if len(wants) != len(gots): + return got + offset = 0 + for w, g in zip(wants, gots): + fraction: Optional[str] = w.group("fraction") + exponent: Optional[str] = w.group("exponent1") + if exponent is None: + exponent = w.group("exponent2") + if fraction is None: + precision = 0 + else: + precision = len(fraction) + if exponent is not None: + precision -= int(exponent) + if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): + # They're close enough. Replace the text we actually + # got with the text we want, so that it will match when we + # check the string literally. + got = ( + got[: g.start() + offset] + w.group() + got[g.end() + offset :] + ) + offset += w.end() - w.start() - (g.end() - g.start()) + return got + + return LiteralsOutputChecker + + +def _get_checker() -> "doctest.OutputChecker": + """Return a doctest.OutputChecker subclass that supports some + additional options: + + * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' + prefixes (respectively) in string literals. Useful when the same + doctest should run in Python 2 and Python 3. + + * NUMBER to ignore floating-point differences smaller than the + precision of the literal number in the doctest. + + An inner class is used to avoid importing "doctest" at the module + level. + """ + global CHECKER_CLASS + if CHECKER_CLASS is None: + CHECKER_CLASS = _init_checker_class() + return CHECKER_CLASS() + + +def _get_allow_unicode_flag() -> int: + """Register and return the ALLOW_UNICODE flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_UNICODE") + + +def _get_allow_bytes_flag() -> int: + """Register and return the ALLOW_BYTES flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_BYTES") + + +def _get_number_flag() -> int: + """Register and return the NUMBER flag.""" + import doctest + + return doctest.register_optionflag("NUMBER") + + +def _get_report_choice(key: str) -> int: + """Return the actual `doctest` module flag value. + + We want to do it as late as possible to avoid importing `doctest` and all + its dependencies when parsing options, as it adds overhead and breaks tests. + """ + import doctest + + return { + DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, + DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, + DOCTEST_REPORT_CHOICE_NONE: 0, + }[key] + + +@pytest.fixture(scope="session") +def doctest_namespace() -> Dict[str, Any]: + """Fixture that returns a :py:class:`dict` that will be injected into the + namespace of doctests.""" + return dict() From 96e4e9e355ccd6c0d9e9b6275db484d56ed97815 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 30 Oct 2021 04:24:58 +0300 Subject: [PATCH 1662/3726] Pytest ipdoctest plugin Based on Pytest doctest.py with changes ported from Nose ipdoctest plugin. --- IPython/core/tests/test_magic.py | 2 +- IPython/testing/plugin/pytest_ipdoctest.py | 230 +++++++++++++-------- appveyor.yml | 2 +- pytest.ini | 41 ++++ 4 files changed, 189 insertions(+), 86 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 37432ca309d..8dd15dcd824 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -584,7 +584,7 @@ def test_xdel(self): def doctest_who(): """doctest for %who - In [1]: %reset -f + In [1]: %reset -sf In [2]: alpha = 123 diff --git a/IPython/testing/plugin/pytest_ipdoctest.py b/IPython/testing/plugin/pytest_ipdoctest.py index 64e8f0e0eee..a58d0868015 100644 --- a/IPython/testing/plugin/pytest_ipdoctest.py +++ b/IPython/testing/plugin/pytest_ipdoctest.py @@ -1,6 +1,13 @@ -"""Discover and run doctests in modules and test files.""" +# Based on Pytest doctest.py +# Original license: +# The MIT License (MIT) +# +# Copyright (c) 2004-2021 Holger Krekel and others +"""Discover and run ipdoctests in modules and test files.""" +import builtins import bdb import inspect +import os import platform import sys import traceback @@ -59,56 +66,56 @@ # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None +CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None def pytest_addoption(parser: Parser) -> None: parser.addini( - "doctest_optionflags", - "option flags for doctests", + "ipdoctest_optionflags", + "option flags for ipdoctests", type="args", default=["ELLIPSIS"], ) parser.addini( - "doctest_encoding", "encoding used for doctest files", default="utf-8" + "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8" ) group = parser.getgroup("collect") group.addoption( - "--doctest-modules", + "--ipdoctest-modules", action="store_true", default=False, - help="run doctests in all .py modules", - dest="doctestmodules", + help="run ipdoctests in all .py modules", + dest="ipdoctestmodules", ) group.addoption( - "--doctest-report", + "--ipdoctest-report", type=str.lower, default="udiff", - help="choose another output format for diffs on doctest failure", + help="choose another output format for diffs on ipdoctest failure", choices=DOCTEST_REPORT_CHOICES, - dest="doctestreport", + dest="ipdoctestreport", ) group.addoption( - "--doctest-glob", + "--ipdoctest-glob", action="append", default=[], metavar="pat", - help="doctests file matching pattern, default: test*.txt", - dest="doctestglob", + help="ipdoctests file matching pattern, default: test*.txt", + dest="ipdoctestglob", ) group.addoption( - "--doctest-ignore-import-errors", + "--ipdoctest-ignore-import-errors", action="store_true", default=False, - help="ignore doctest ImportErrors", - dest="doctest_ignore_import_errors", + help="ignore ipdoctest ImportErrors", + dest="ipdoctest_ignore_import_errors", ) group.addoption( - "--doctest-continue-on-failure", + "--ipdoctest-continue-on-failure", action="store_true", default=False, - help="for a given doctest, continue to run after the first failure", - dest="doctest_continue_on_failure", + help="for a given ipdoctest, continue to run after the first failure", + dest="ipdoctest_continue_on_failure", ) @@ -119,15 +126,16 @@ def pytest_unconfigure() -> None: def pytest_collect_file( - path: py.path.local, parent: Collector, -) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: + path: py.path.local, + parent: Collector, +) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]: config = parent.config if path.ext == ".py": - if config.option.doctestmodules and not _is_setup_py(path): - mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) + if config.option.ipdoctestmodules and not _is_setup_py(path): + mod: IPDoctestModule = IPDoctestModule.from_parent(parent, fspath=path) return mod - elif _is_doctest(config, path, parent): - txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) + elif _is_ipdoctest(config, path, parent): + txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, fspath=path) return txt return None @@ -139,10 +147,10 @@ def _is_setup_py(path: py.path.local) -> bool: return b"setuptools" in contents or b"distutils" in contents -def _is_doctest(config: Config, path: py.path.local, parent) -> bool: +def _is_ipdoctest(config: Config, path: py.path.local, parent) -> bool: if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): return True - globs = config.getoption("doctestglob") or ["test*.txt"] + globs = config.getoption("ipdoctestglob") or ["test*.txt"] for glob in globs: if path.check(fnmatch=glob): return True @@ -168,10 +176,11 @@ def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: self.failures = failures -def _init_runner_class() -> Type["doctest.DocTestRunner"]: +def _init_runner_class() -> Type["IPDocTestRunner"]: import doctest + from .ipdoctest import IPDocTestRunner - class PytestDoctestRunner(doctest.DebugRunner): + class PytestDoctestRunner(IPDocTestRunner): """Runner to collect failures. Note that the out variable in this case is a list instead of a @@ -180,18 +189,20 @@ class PytestDoctestRunner(doctest.DebugRunner): def __init__( self, - checker: Optional["doctest.OutputChecker"] = None, + checker: Optional["IPDoctestOutputChecker"] = None, verbose: Optional[bool] = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: - doctest.DebugRunner.__init__( - self, checker=checker, verbose=verbose, optionflags=optionflags - ) + super().__init__(checker=checker, verbose=verbose, optionflags=optionflags) self.continue_on_failure = continue_on_failure def report_failure( - self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) if self.continue_on_failure: @@ -220,11 +231,11 @@ def report_unexpected_exception( def _get_runner( - checker: Optional["doctest.OutputChecker"] = None, + checker: Optional["IPDoctestOutputChecker"] = None, verbose: Optional[bool] = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> "doctest.DocTestRunner": +) -> "IPDocTestRunner": # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -239,12 +250,12 @@ def _get_runner( ) -class DoctestItem(pytest.Item): +class IPDoctestItem(pytest.Item): def __init__( self, name: str, - parent: "Union[DoctestTextfile, DoctestModule]", - runner: Optional["doctest.DocTestRunner"] = None, + parent: "Union[IPDoctestTextfile, IPDoctestModule]", + runner: Optional["IPDocTestRunner"] = None, dtest: Optional["doctest.DocTest"] = None, ) -> None: super().__init__(name, parent) @@ -256,10 +267,10 @@ def __init__( @classmethod def from_parent( # type: ignore cls, - parent: "Union[DoctestTextfile, DoctestModule]", + parent: "Union[IPDoctestTextfile, IPDoctestModule]", *, name: str, - runner: "doctest.DocTestRunner", + runner: "IPDocTestRunner", dtest: "doctest.DocTest", ): # incompatible signature due to to imposed limits on sublcass @@ -271,25 +282,70 @@ def setup(self) -> None: self.fixture_request = _setup_fixtures(self) globs = dict(getfixture=self.fixture_request.getfixturevalue) for name, value in self.fixture_request.getfixturevalue( - "doctest_namespace" + "ipdoctest_namespace" ).items(): globs[name] = value self.dtest.globs.update(globs) + from .ipdoctest import IPExample + + if isinstance(self.dtest.examples[0], IPExample): + # for IPython examples *only*, we swap the globals with the ipython + # namespace, after updating it with the globals (which doctest + # fills with the necessary info from the module being tested). + self._user_ns_orig = {} + self._user_ns_orig.update(_ip.user_ns) + _ip.user_ns.update(self.dtest.globs) + # We must remove the _ key in the namespace, so that Python's + # doctest code sets it naturally + _ip.user_ns.pop("_", None) + _ip.user_ns["__builtins__"] = builtins + self.dtest.globs = _ip.user_ns + + def teardown(self) -> None: + from .ipdoctest import IPExample + + # Undo the test.globs reassignment we made + if isinstance(self.dtest.examples[0], IPExample): + self.dtest.globs = {} + _ip.user_ns.clear() + _ip.user_ns.update(self._user_ns_orig) + del self._user_ns_orig + + self.dtest.globs.clear() + def runtest(self) -> None: assert self.dtest is not None assert self.runner is not None _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() failures: List["doctest.DocTestFailure"] = [] - # Type ignored because we change the type of `out` from what - # doctest expects. - self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] + + # exec(compile(..., "single", ...), ...) puts result in builtins._ + had_underscore_value = hasattr(builtins, "_") + underscore_original_value = getattr(builtins, "_", None) + + # Save our current directory and switch out to the one where the + # test was originally created, in case another doctest did a + # directory change. We'll restore this in the finally clause. + curdir = os.getcwd() + os.chdir(self.fspath.dirname) + try: + # Type ignored because we change the type of `out` from what + # ipdoctest expects. + self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type] + finally: + os.chdir(curdir) + if had_underscore_value: + setattr(builtins, "_", underscore_original_value) + elif hasattr(builtins, "_"): + delattr(builtins, "_") + if failures: raise MultipleDoctestFailures(failures) def _disable_output_capturing_for_darwin(self) -> None: - """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" + """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985).""" if platform.system() != "Darwin": return capman = self.config.pluginmanager.getplugin("capturemanager") @@ -301,13 +357,14 @@ def _disable_output_capturing_for_darwin(self) -> None: # TODO: Type ignored -- breaks Liskov Substitution. def repr_failure( # type: ignore[override] - self, excinfo: ExceptionInfo[BaseException], + self, + excinfo: ExceptionInfo[BaseException], ) -> Union[str, TerminalRepr]: import doctest failures: Optional[ Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = (None) + ] = None if isinstance( excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): @@ -330,7 +387,7 @@ def repr_failure( # type: ignore[override] reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] checker = _get_checker() report_choice = _get_report_choice( - self.config.getoption("doctestreport") + self.config.getoption("ipdoctestreport") ) if lineno is not None: assert failure.test.docstring is not None @@ -369,7 +426,7 @@ def repr_failure( # type: ignore[override] def reportinfo(self): assert self.dtest is not None - return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name + return self.fspath, self.dtest.lineno, "[ipdoctest] %s" % self.name def _get_flag_lookup() -> Dict[str, int]: @@ -389,7 +446,7 @@ def _get_flag_lookup() -> Dict[str, int]: def get_optionflags(parent): - optionflags_str = parent.config.getini("doctest_optionflags") + optionflags_str = parent.config.getini("ipdoctest_optionflags") flag_lookup_table = _get_flag_lookup() flag_acc = 0 for flag in optionflags_str: @@ -398,7 +455,7 @@ def get_optionflags(parent): def _get_continue_on_failure(config): - continue_on_failure = config.getvalue("doctest_continue_on_failure") + continue_on_failure = config.getvalue("ipdoctest_continue_on_failure") if continue_on_failure: # We need to turn off this if we use pdb since we should stop at # the first failure. @@ -407,15 +464,16 @@ def _get_continue_on_failure(config): return continue_on_failure -class DoctestTextfile(pytest.Module): +class IPDoctestTextfile(pytest.Module): obj = None - def collect(self) -> Iterable[DoctestItem]: + def collect(self) -> Iterable[IPDoctestItem]: import doctest + from .ipdoctest import IPDocTestParser # Inspired by doctest.testfile; ideally we would use it directly, # but it doesn't support passing a custom checker. - encoding = self.config.getini("doctest_encoding") + encoding = self.config.getini("ipdoctest_encoding") text = self.fspath.read_text(encoding) filename = str(self.fspath) name = self.fspath.basename @@ -430,10 +488,10 @@ def collect(self) -> Iterable[DoctestItem]: continue_on_failure=_get_continue_on_failure(self.config), ) - parser = doctest.DocTestParser() + parser = IPDocTestParser() test = parser.get_doctest(text, globs, name, filename, 0) if test.examples: - yield DoctestItem.from_parent( + yield IPDoctestItem.from_parent( self, name=test.name, runner=runner, dtest=test ) @@ -487,12 +545,13 @@ def _mock_aware_unwrap( inspect.unwrap = real_unwrap -class DoctestModule(pytest.Module): - def collect(self) -> Iterable[DoctestItem]: +class IPDoctestModule(pytest.Module): + def collect(self) -> Iterable[IPDoctestItem]: import doctest + from .ipdoctest import DocTestFinder, IPDocTestParser - class MockAwareDocTestFinder(doctest.DocTestFinder): - """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. + class MockAwareDocTestFinder(DocTestFinder): + """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug. https://github.com/pytest-dev/pytest/issues/3456 https://bugs.python.org/issue25532 @@ -507,8 +566,10 @@ def _find_lineno(self, obj, source_lines): if isinstance(obj, property): obj = getattr(obj, "fget", obj) # Type ignored because this is a private function. - return doctest.DocTestFinder._find_lineno( # type: ignore - self, obj, source_lines, + return DocTestFinder._find_lineno( # type: ignore + self, + obj, + source_lines, ) def _find( @@ -519,7 +580,7 @@ def _find( with _patch_unwrap_mock_aware(): # Type ignored because this is a private function. - doctest.DocTestFinder._find( # type: ignore + DocTestFinder._find( # type: ignore self, tests, obj, name, module, source_lines, globs, seen ) @@ -531,12 +592,12 @@ def _find( try: module = import_path(self.fspath) except ImportError: - if self.config.getvalue("doctest_ignore_import_errors"): + if self.config.getvalue("ipdoctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.fspath) else: raise # Uses internal doctest module parsing mechanism. - finder = MockAwareDocTestFinder() + finder = MockAwareDocTestFinder(parser=IPDocTestParser()) optionflags = get_optionflags(self) runner = _get_runner( verbose=False, @@ -546,14 +607,14 @@ def _find( ) for test in finder.find(module, module.__name__): - if test.examples: # skip empty doctests - yield DoctestItem.from_parent( + if test.examples: # skip empty ipdoctests + yield IPDoctestItem.from_parent( self, name=test.name, runner=runner, dtest=test ) -def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: - """Used by DoctestTextfile and DoctestItem to setup fixture information.""" +def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest: + """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information.""" def func() -> None: pass @@ -568,11 +629,12 @@ def func() -> None: return fixture_request -def _init_checker_class() -> Type["doctest.OutputChecker"]: +def _init_checker_class() -> Type["IPDoctestOutputChecker"]: import doctest import re + from .ipdoctest import IPDoctestOutputChecker - class LiteralsOutputChecker(doctest.OutputChecker): + class LiteralsOutputChecker(IPDoctestOutputChecker): # Based on doctest_nose_plugin.py from the nltk project # (https://github.com/nltk/nltk) and on the "numtest" doctest extension # by Sebastien Boisgerault (https://github.com/boisgera/numtest). @@ -603,7 +665,7 @@ class LiteralsOutputChecker(doctest.OutputChecker): ) def check_output(self, want: str, got: str, optionflags: int) -> bool: - if doctest.OutputChecker.check_output(self, want, got, optionflags): + if IPDoctestOutputChecker.check_output(self, want, got, optionflags): return True allow_unicode = optionflags & _get_allow_unicode_flag() @@ -627,7 +689,7 @@ def remove_prefixes(regex: Pattern[str], txt: str) -> str: if allow_number: got = self._remove_unwanted_precision(want, got) - return doctest.OutputChecker.check_output(self, want, got, optionflags) + return IPDoctestOutputChecker.check_output(self, want, got, optionflags) def _remove_unwanted_precision(self, want: str, got: str) -> str: wants = list(self._number_re.finditer(want)) @@ -659,18 +721,18 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return LiteralsOutputChecker -def _get_checker() -> "doctest.OutputChecker": - """Return a doctest.OutputChecker subclass that supports some +def _get_checker() -> "IPDoctestOutputChecker": + """Return a IPDoctestOutputChecker subclass that supports some additional options: * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' prefixes (respectively) in string literals. Useful when the same - doctest should run in Python 2 and Python 3. + ipdoctest should run in Python 2 and Python 3. * NUMBER to ignore floating-point differences smaller than the - precision of the literal number in the doctest. + precision of the literal number in the ipdoctest. - An inner class is used to avoid importing "doctest" at the module + An inner class is used to avoid importing "ipdoctest" at the module level. """ global CHECKER_CLASS @@ -701,9 +763,9 @@ def _get_number_flag() -> int: def _get_report_choice(key: str) -> int: - """Return the actual `doctest` module flag value. + """Return the actual `ipdoctest` module flag value. - We want to do it as late as possible to avoid importing `doctest` and all + We want to do it as late as possible to avoid importing `ipdoctest` and all its dependencies when parsing options, as it adds overhead and breaks tests. """ import doctest @@ -718,7 +780,7 @@ def _get_report_choice(key: str) -> int: @pytest.fixture(scope="session") -def doctest_namespace() -> Dict[str, Any]: +def ipdoctest_namespace() -> Dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the - namespace of doctests.""" + namespace of ipdoctests.""" return dict() diff --git a/appveyor.yml b/appveyor.yml index 26df940d77e..8f9841f5e7e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,7 @@ install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib pandas - - pip install .[test] + - pip install -e .[test] - mkdir results - cd results test_script: diff --git a/pytest.ini b/pytest.ini index 9da365334c6..2287272b8a9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,43 @@ [pytest] addopts = --durations=10 + -p IPython.testing.plugin.pytest_ipdoctest --ipdoctest-modules + --ignore=docs + --ignore=examples + --ignore=htmlcov + --ignore=ipython_kernel + --ignore=ipython_parallel + --ignore=results + --ignore=tmp + --ignore=tools + --ignore=traitlets + --ignore=IPython/core/tests/daft_extension + --ignore=IPython/sphinxext + --ignore=IPython/terminal/pt_inputhooks + --ignore=IPython/__main__.py + --ignore=IPython/config.py + --ignore=IPython/frontend.py + --ignore=IPython/html.py + --ignore=IPython/nbconvert.py + --ignore=IPython/nbformat.py + --ignore=IPython/parallel.py + --ignore=IPython/qt.py + --ignore=IPython/external/qt_for_kernel.py + --ignore=IPython/html/widgets/widget_link.py + --ignore=IPython/html/widgets/widget_output.py + --ignore=IPython/lib/inputhookglut.py + --ignore=IPython/lib/inputhookgtk.py + --ignore=IPython/lib/inputhookgtk3.py + --ignore=IPython/lib/inputhookgtk4.py + --ignore=IPython/lib/inputhookpyglet.py + --ignore=IPython/lib/inputhookqt4.py + --ignore=IPython/lib/inputhookwx.py + --ignore=IPython/terminal/console.py + --ignore=IPython/terminal/ptshell.py + --ignore=IPython/utils/_process_cli.py + --ignore=IPython/utils/_process_posix.py + --ignore=IPython/utils/_process_win32.py + --ignore=IPython/utils/_process_win32_controller.py + --ignore=IPython/utils/daemonize.py + --ignore=IPython/utils/eventful.py +doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS +ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS From 7617110e9f3cf8b97991ce3727631f8ed800a571 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 3 Nov 2021 22:42:00 +0300 Subject: [PATCH 1663/3726] Add builtins._ doctest Defining builtins._ should not break anything outside the doctest while also should be working as expected inside the doctest. --- IPython/testing/plugin/test_ipdoctest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/IPython/testing/plugin/test_ipdoctest.py b/IPython/testing/plugin/test_ipdoctest.py index d8f59916369..2686172bb29 100644 --- a/IPython/testing/plugin/test_ipdoctest.py +++ b/IPython/testing/plugin/test_ipdoctest.py @@ -74,3 +74,19 @@ def doctest_multiline3(): In [15]: h(0) Out[15]: -1 """ + + +def doctest_builtin_underscore(): + """Defining builtins._ should not break anything outside the doctest + while also should be working as expected inside the doctest. + + In [1]: import builtins + + In [2]: builtins._ = 42 + + In [3]: builtins._ + Out[3]: 42 + + In [4]: _ + Out[4]: 42 + """ From 2f9407e80c2d615d5c3d7d79fee31f7c1bffad20 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 4 Nov 2021 23:56:16 +0300 Subject: [PATCH 1664/3726] Skip test_pylabtools when no matplotlib installed Currently Pytest will bail out the whole suit because of ImportError. --- IPython/core/tests/test_pylabtools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 64acd3d465a..579e647e078 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -8,14 +8,15 @@ from binascii import a2b_base64 from io import BytesIO -import matplotlib +import pytest + +matplotlib = pytest.importorskip("matplotlib") matplotlib.use('Agg') from matplotlib.figure import Figure from matplotlib import pyplot as plt from matplotlib_inline import backend_inline import numpy as np -import pytest from IPython.core.getipython import get_ipython from IPython.core.interactiveshell import InteractiveShell From 27a33628310cbd68632f0e8b514de731a033f8e6 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 5 Nov 2021 00:00:03 +0300 Subject: [PATCH 1665/3726] Make test_shim_warning not fail on unrelated warnings --- IPython/utils/tests/test_shimmodule.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/IPython/utils/tests/test_shimmodule.py b/IPython/utils/tests/test_shimmodule.py index 3a1561755a6..814e48240a9 100644 --- a/IPython/utils/tests/test_shimmodule.py +++ b/IPython/utils/tests/test_shimmodule.py @@ -1,13 +1,10 @@ +import pytest import sys -import warnings from IPython.utils.shimmodule import ShimWarning def test_shim_warning(): sys.modules.pop('IPython.config', None) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") + with pytest.warns(ShimWarning): import IPython.config - assert len(w) == 1 - assert issubclass(w[-1].category, ShimWarning) From 201685e1f00049d3b8ec4bc68eefc8451783f5fe Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 2 Nov 2021 04:34:40 +0300 Subject: [PATCH 1666/3726] Revive doctest_tb_sysexit Output changed with `pure-eval>=0.2.0` which learned to parse binary operators and to show its arguments. Test changed to not use the binary operator '%' to pass on any version. --- IPython/core/tests/simpleerr.py | 2 +- IPython/core/tests/test_iplib.py | 162 +++++++++++++++---------------- 2 files changed, 78 insertions(+), 86 deletions(-) diff --git a/IPython/core/tests/simpleerr.py b/IPython/core/tests/simpleerr.py index 34e697029db..5ba5084a1c4 100644 --- a/IPython/core/tests/simpleerr.py +++ b/IPython/core/tests/simpleerr.py @@ -8,7 +8,7 @@ def div0(): x/y def sysexit(stat, mode): - raise SystemExit(stat, 'Mode = %s' % mode) + raise SystemExit(stat, f'Mode = {mode}') def bar(mode): "bar" diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index 6804c2b2d3d..0ee1b8da8c4 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -118,91 +118,83 @@ def doctest_tb_verbose(): """ -# TODO : Marc 2021 – this seem to fail due -# to upstream changes in CI for whatever reason. -# Commenting for now, to revive someday (maybe?) -# nose won't work in 3.10 anyway and we'll have to disable iptest. -# thus this likely need to bemigrated to pytest. - - -# def doctest_tb_sysexit(): -# """ -# In [17]: %xmode plain -# Exception reporting mode: Plain -# -# In [18]: %run simpleerr.py exit -# An exception has occurred, use %tb to see the full traceback. -# SystemExit: (1, 'Mode = exit') -# -# In [19]: %run simpleerr.py exit 2 -# An exception has occurred, use %tb to see the full traceback. -# SystemExit: (2, 'Mode = exit') -# -# In [20]: %tb -# Traceback (most recent call last): -# File ... in -# bar(mode) -# File ... line 22, in bar -# sysexit(stat, mode) -# File ... line 11, in sysexit -# raise SystemExit(stat, 'Mode = %s' % mode) -# SystemExit: (2, 'Mode = exit') -# -# In [21]: %xmode context -# Exception reporting mode: Context -# -# In [22]: %tb -# --------------------------------------------------------------------------- -# SystemExit Traceback (most recent call last) -# -# ... -# 29 except IndexError: -# 30 mode = 'div' -# ---> 32 bar(mode) -# -# ...bar(mode) -# 20 except: -# 21 stat = 1 -# ---> 22 sysexit(stat, mode) -# 23 else: -# 24 raise ValueError('Unknown mode') -# -# ...sysexit(stat, mode) -# 10 def sysexit(stat, mode): -# ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) -# -# SystemExit: (2, 'Mode = exit') -# -# In [23]: %xmode verbose -# Exception reporting mode: Verbose -# -# In [24]: %tb -# --------------------------------------------------------------------------- -# SystemExit Traceback (most recent call last) -# -# ... in -# 29 except IndexError: -# 30 mode = 'div' -# ---> 32 bar(mode) -# mode = 'exit' -# -# ... in bar(mode='exit') -# 20 except: -# 21 stat = 1 -# ---> 22 sysexit(stat, mode) -# mode = 'exit' -# stat = 2 -# 23 else: -# 24 raise ValueError('Unknown mode') -# -# ... in sysexit(stat=2, mode='exit') -# 10 def sysexit(stat, mode): -# ---> 11 raise SystemExit(stat, 'Mode = %s' % mode) -# stat = 2 -# mode = 'exit' -# -# SystemExit: (2, 'Mode = exit') -# """ +def doctest_tb_sysexit(): + """ + In [17]: %xmode plain + Exception reporting mode: Plain + + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') + + In [20]: %tb + Traceback (most recent call last): + File ... in + bar(mode) + File ... line 22, in bar + sysexit(stat, mode) + File ... line 11, in sysexit + raise SystemExit(stat, f'Mode = {mode}') + SystemExit: (2, 'Mode = exit') + + In [21]: %xmode context + Exception reporting mode: Context + + In [22]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + + ... + 29 except IndexError: + 30 mode = 'div' + ---> 32 bar(mode) + + ...bar(mode) + 20 except: + 21 stat = 1 + ---> 22 sysexit(stat, mode) + 23 else: + 24 raise ValueError('Unknown mode') + + ...sysexit(stat, mode) + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f'Mode = {mode}') + + SystemExit: (2, 'Mode = exit') + + In [23]: %xmode verbose + Exception reporting mode: Verbose + + In [24]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + + ... in + 29 except IndexError: + 30 mode = 'div' + ---> 32 bar(mode) + mode = 'exit' + + ... in bar(mode='exit') + 20 except: + 21 stat = 1 + ---> 22 sysexit(stat, mode) + mode = 'exit' + stat = 2 + 23 else: + 24 raise ValueError('Unknown mode') + + ... in sysexit(stat=2, mode='exit') + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f'Mode = {mode}') + stat = 2 + + SystemExit: (2, 'Mode = exit') + """ def test_run_cell(): From 31a08778f65af8ce597d4ee1a900724d31d49041 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 6 Nov 2021 19:38:39 -0700 Subject: [PATCH 1667/3726] attempt reformat --- IPython/core/tests/simpleerr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/tests/simpleerr.py b/IPython/core/tests/simpleerr.py index 5ba5084a1c4..f85e2d6b925 100644 --- a/IPython/core/tests/simpleerr.py +++ b/IPython/core/tests/simpleerr.py @@ -8,7 +8,8 @@ def div0(): x/y def sysexit(stat, mode): - raise SystemExit(stat, f'Mode = {mode}') + raise SystemExit(stat, f"Mode = {mode}") + def bar(mode): "bar" From 023409c714300777b516da79919e406cd579934c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 6 Nov 2021 20:47:43 -0700 Subject: [PATCH 1668/3726] rectify line numbers --- IPython/core/tests/test_iplib.py | 142 +++++++++++++++++-------------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index 0ee1b8da8c4..5cbc77251db 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -43,15 +43,15 @@ def doctest_tb_plain(): In [18]: xmode plain Exception reporting mode: Plain -In [19]: run simpleerr.py -Traceback (most recent call last): - ...line 32, in - bar(mode) - ...line 16, in bar - div0() - ...line 8, in div0 - x/y -ZeroDivisionError: ... + In [19]: run simpleerr.py + Traceback (most recent call last): + ...line ..., in + bar(mode) + ...line ..., in bar + div0() + ...line ..., in div0 + x/y + ZeroDivisionError: ... """ @@ -60,29 +60,28 @@ def doctest_tb_context(): In [3]: xmode context Exception reporting mode: Context -In [4]: run simpleerr.py ---------------------------------------------------------------------------- -ZeroDivisionError Traceback (most recent call last) - -... in - 29 except IndexError: - 30 mode = 'div' ----> 32 bar(mode) - -... in bar(mode) - 14 "bar" - 15 if mode=='div': ----> 16 div0() - 17 elif mode=='exit': - 18 try: - -... in div0() - 6 x = 1 - 7 y = 0 -----> 8 x/y - -ZeroDivisionError: ... -""" + In [4]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... in + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ... in bar(mode) + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + + ZeroDivisionError: ...""" def doctest_tb_verbose(): @@ -95,17 +94,17 @@ def doctest_tb_verbose(): ZeroDivisionError Traceback (most recent call last) ... in - 29 except IndexError: - 30 mode = 'div' - ---> 32 bar(mode) + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) mode = 'div' ... in bar(mode='div') - 14 "bar" - 15 if mode=='div': - ---> 16 div0() - 17 elif mode=='exit': - 18 try: + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: ... in div0() 6 x = 1 @@ -133,12 +132,14 @@ def doctest_tb_sysexit(): In [20]: %tb Traceback (most recent call last): - File ... in + File ..., in execfile + exec(compiler(f.read(), fname, 'exec'), glob, loc) + File ..., in bar(mode) - File ... line 22, in bar + File ..., in bar sysexit(stat, mode) - File ... line 11, in sysexit - raise SystemExit(stat, f'Mode = {mode}') + File ..., in sysexit + raise SystemExit(stat, f"Mode = {mode}") SystemExit: (2, 'Mode = exit') In [21]: %xmode context @@ -147,24 +148,39 @@ def doctest_tb_sysexit(): In [22]: %tb --------------------------------------------------------------------------- SystemExit Traceback (most recent call last) - + File ..., in execfile(fname, glob, loc, compiler) + 70 with open(fname, 'rb') as f: + 71 compiler = compiler or compile + ---> 72 exec(compiler(f.read(), fname, 'exec'), glob, loc) ... - 29 except IndexError: - 30 mode = 'div' - ---> 32 bar(mode) + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) ...bar(mode) - 20 except: - 21 stat = 1 - ---> 22 sysexit(stat, mode) - 23 else: - 24 raise ValueError('Unknown mode') + 21 except: + 22 stat = 1 + ---> 23 sysexit(stat, mode) + 24 else: + 25 raise ValueError('Unknown mode') ...sysexit(stat, mode) 10 def sysexit(stat, mode): - ---> 11 raise SystemExit(stat, f'Mode = {mode}') + ---> 11 raise SystemExit(stat, f"Mode = {mode}") SystemExit: (2, 'Mode = exit') + """ + + +def doctest_tb_sysexit_verbose(): + """ + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') In [23]: %xmode verbose Exception reporting mode: Verbose @@ -174,23 +190,23 @@ def doctest_tb_sysexit(): SystemExit Traceback (most recent call last) ... in - 29 except IndexError: - 30 mode = 'div' - ---> 32 bar(mode) + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) mode = 'exit' ... in bar(mode='exit') - 20 except: - 21 stat = 1 - ---> 22 sysexit(stat, mode) + ... except: + ... stat = 1 + ---> ... sysexit(stat, mode) mode = 'exit' stat = 2 - 23 else: - 24 raise ValueError('Unknown mode') + ... else: + ... raise ValueError('Unknown mode') ... in sysexit(stat=2, mode='exit') 10 def sysexit(stat, mode): - ---> 11 raise SystemExit(stat, f'Mode = {mode}') + ---> 11 raise SystemExit(stat, f"Mode = {mode}") stat = 2 SystemExit: (2, 'Mode = exit') From ecb0bd663e2f2bd5177b3983c15cfdc81375f658 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 7 Nov 2021 18:48:33 +0300 Subject: [PATCH 1669/3726] Split coverage into distinct library and tests stats Some projects exclude tests coverage completely because counting it with other code artificially increases total code coverage, but tests coverage monitoring is a good thing because it catches mistakenly not running tests. The split should eliminate situations with 'coverage decreases' status reports when tests code shrinks while the library code and its coverage remains the same. --- codecov.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index d65a8d9cfdf..f82fcfc7b87 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,7 +2,12 @@ coverage: status: patch: off project: - default: + default: false + library: target: auto + paths: '!.*/tests/.*' + tests: + target: auto + paths: '.*/tests/.*' codecov: require_ci_to_pass: false From ebdea82dfa0638576c91c510eca46e9f7c57b385 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 8 Nov 2021 02:49:12 +0300 Subject: [PATCH 1670/3726] Suppress bunch of self-deprecation warnings And exclude these deprecated modules from coverage report --- IPython/core/tests/test_display.py | 14 +++++++++----- IPython/core/tests/test_pylabtools.py | 6 +++--- codecov.yml | 12 ++++++++++++ pytest.ini | 18 +++++++++++------- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index b88bee8c8c1..c6e13b917c6 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -161,9 +161,11 @@ def _get_inline_config(): def test_set_matplotlib_close(): cfg = _get_inline_config() cfg.close_figures = False - display.set_matplotlib_close() + with pytest.deprecated_call(): + display.set_matplotlib_close() assert cfg.close_figures - display.set_matplotlib_close(False) + with pytest.deprecated_call(): + display.set_matplotlib_close(False) assert not cfg.close_figures _fmt_mime_map = { @@ -185,7 +187,8 @@ def test_set_matplotlib_formats(): (), ]: active_mimes = {_fmt_mime_map[fmt] for fmt in formats} - display.set_matplotlib_formats(*formats) + with pytest.deprecated_call(): + display.set_matplotlib_formats(*formats) for mime, f in formatters.items(): if mime in active_mimes: assert Figure in f @@ -201,8 +204,9 @@ def test_set_matplotlib_formats_kwargs(): cfg = _get_inline_config() cfg.print_figure_kwargs.update(dict(foo='bar')) kwargs = dict(dpi=150) - display.set_matplotlib_formats('png', **kwargs) - formatter = ip.display_formatter.formatters['image/png'] + with pytest.deprecated_call(): + display.set_matplotlib_formats("png", **kwargs) + formatter = ip.display_formatter.formatters["image/png"] f = formatter.lookup_by_type(Figure) formatter_kwargs = f.keywords expected = kwargs diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 579e647e078..78886373ced 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -102,9 +102,9 @@ def test_select_figure_formats_str(): def test_select_figure_formats_kwargs(): ip = get_ipython() - kwargs = dict(quality=10, bbox_inches='tight') - pt.select_figure_formats(ip, 'png', **kwargs) - formatter = ip.display_formatter.formatters['image/png'] + kwargs = dict(bbox_inches="tight") + pt.select_figure_formats(ip, "png", **kwargs) + formatter = ip.display_formatter.formatters["image/png"] f = formatter.lookup_by_type(Figure) cell = f.keywords expected = kwargs diff --git a/codecov.yml b/codecov.yml index d65a8d9cfdf..dc3681c3dc4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,3 +6,15 @@ coverage: target: auto codecov: require_ci_to_pass: false + +ignore: + - IPython/kernel/* + - IPython/consoleapp.py + - IPython/core/inputsplitter.py + - IPython/lib/inputhook*.py + - IPython/lib/kernel.py + - IPython/utils/jsonutil.py + - IPython/utils/localinterfaces.py + - IPython/utils/log.py + - IPython/utils/signatures.py + - IPython/utils/traitlets.py diff --git a/pytest.ini b/pytest.ini index 2287272b8a9..3aeec55e754 100644 --- a/pytest.ini +++ b/pytest.ini @@ -24,13 +24,6 @@ addopts = --durations=10 --ignore=IPython/external/qt_for_kernel.py --ignore=IPython/html/widgets/widget_link.py --ignore=IPython/html/widgets/widget_output.py - --ignore=IPython/lib/inputhookglut.py - --ignore=IPython/lib/inputhookgtk.py - --ignore=IPython/lib/inputhookgtk3.py - --ignore=IPython/lib/inputhookgtk4.py - --ignore=IPython/lib/inputhookpyglet.py - --ignore=IPython/lib/inputhookqt4.py - --ignore=IPython/lib/inputhookwx.py --ignore=IPython/terminal/console.py --ignore=IPython/terminal/ptshell.py --ignore=IPython/utils/_process_cli.py @@ -39,5 +32,16 @@ addopts = --durations=10 --ignore=IPython/utils/_process_win32_controller.py --ignore=IPython/utils/daemonize.py --ignore=IPython/utils/eventful.py + + --ignore=IPython/kernel + --ignore=IPython/consoleapp.py + --ignore=IPython/core/inputsplitter.py + --ignore-glob=IPython/lib/inputhook*.py + --ignore=IPython/lib/kernel.py + --ignore=IPython/utils/jsonutil.py + --ignore=IPython/utils/localinterfaces.py + --ignore=IPython/utils/log.py + --ignore=IPython/utils/signatures.py + --ignore=IPython/utils/traitlets.py doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS From c94e49d0b297a5ef91c1e64137be1bcc672cf012 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Mon, 8 Nov 2021 09:22:27 -0500 Subject: [PATCH 1671/3726] Fixes race condition in get_ipython_dir() Fixes https://github.com/ipython/ipython/issues/13255 --- IPython/paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/paths.py b/IPython/paths.py index a2ea5bc1159..e29eea58e20 100644 --- a/IPython/paths.py +++ b/IPython/paths.py @@ -72,7 +72,7 @@ def get_ipython_dir() -> str: " using a temp directory.".format(parent)) ipdir = tempfile.mkdtemp() else: - os.makedirs(ipdir) + ensure_dir_exists(ipdir) assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not." return ipdir From a5c798b38154a8b35bbd52ebb9305d5aa5934fdb Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 8 Nov 2021 22:02:38 +0300 Subject: [PATCH 1672/3726] TST: Remove unneeded requirement on pywin32 The only routine that requires `pywin32` is `win32_clipboard_get` which is not tested. --- IPython/core/tests/test_profile.py | 11 ----------- IPython/core/tests/test_run.py | 5 ----- appveyor.yml | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/IPython/core/tests/test_profile.py b/IPython/core/tests/test_profile.py index 07f43e4ec05..2732a8c8619 100644 --- a/IPython/core/tests/test_profile.py +++ b/IPython/core/tests/test_profile.py @@ -70,15 +70,6 @@ def teardown_module(): #----------------------------------------------------------------------------- # Test functions #----------------------------------------------------------------------------- -def win32_without_pywin32(): - if sys.platform == 'win32': - try: - import pywin32 - except ImportError: - return True - return False - - class ProfileStartupTest(TestCase): def setUp(self): # create profile dir @@ -102,12 +93,10 @@ def init(self, startup_file, startup, test): def validate(self, output): tt.ipexec_validate(self.fname, output, '', options=self.options) - @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") def test_startup_py(self): self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n') self.validate('123') - @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows") def test_startup_ipy(self): self.init('00-start.ipy', '%xmode plain\n', '') self.validate('Exception reporting mode: Plain') diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index c593a82da6a..742b6b27e4b 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -237,11 +237,6 @@ def test_simpledef(self): def test_obj_del(self): """Test that object's __del__ methods are called on exit.""" - if sys.platform == 'win32': - try: - import win32api - except ImportError as e: - raise unittest.SkipTest("Test requires pywin32") from e src = ("class A(object):\n" " def __del__(self):\n" " print('object A deleted')\n" diff --git a/appveyor.yml b/appveyor.yml index 8f9841f5e7e..c3702755110 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib pandas + - pip install nose coverage pytest pytest-cov pytest-trio matplotlib pandas - pip install -e .[test] - mkdir results - cd results From 98372a94598eef79cf10a74c7081b707a853b233 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 8 Nov 2021 22:17:52 +0300 Subject: [PATCH 1673/3726] Add Path support to ipexec --- IPython/core/tests/test_profile.py | 2 +- IPython/testing/tools.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/IPython/core/tests/test_profile.py b/IPython/core/tests/test_profile.py index 2732a8c8619..8dd58cc67f4 100644 --- a/IPython/core/tests/test_profile.py +++ b/IPython/core/tests/test_profile.py @@ -91,7 +91,7 @@ def init(self, startup_file, startup, test): f.write(test) def validate(self, output): - tt.ipexec_validate(self.fname, output, '', options=self.options) + tt.ipexec_validate(self.fname, output, "", options=self.options) def test_startup_py(self): self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n') diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 10bd4b6e0be..d1e57306b9d 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -177,7 +177,7 @@ def ipexec(fname, options=None, commands=()): Parameters ---------- - fname : str + fname : str, Path Name of file to be executed (should have .py or .ipy extension). options : optional, list @@ -200,6 +200,9 @@ def ipexec(fname, options=None, commands=()): # Absolute path for filename full_fname = os.path.join(test_dir, fname) full_cmd = ipython_cmd + cmdargs + ['--', full_fname] + if sys.platform == "win32" and sys.version_info < (3, 8): + # subprocess.Popen does not support Path objects yet + full_cmd = list(map(str, full_cmd)) env = os.environ.copy() # FIXME: ignore all warnings in ipexec while we have shims # should we keep suppressing warnings here, even after removing shims? @@ -232,7 +235,7 @@ def ipexec_validate(fname, expected_out, expected_err='', Parameters ---------- - fname : str + fname : str, Path Name of the file to be executed (should have .py or .ipy extension). expected_out : str From 63ddd77764600175adc5c246bf510227a18ef3db Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 9 Nov 2021 03:39:58 +0300 Subject: [PATCH 1674/3726] Fix codecov.yml syntax It expects `paths` to be a list (though a string had been working previously) but for some reason Codecov will silently ignore mistakes and won't tell you that the file has incorrect syntax. --- codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index 31ab1f18cfb..9a798d0c42d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,10 +5,10 @@ coverage: default: false library: target: auto - paths: '!.*/tests/.*' + paths: ['!.*/tests/.*'] tests: target: auto - paths: '.*/tests/.*' + paths: ['.*/tests/.*'] codecov: require_ci_to_pass: false From f9b36a4f407abf5a6379fe31d3e42ee1698f9639 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Tue, 9 Nov 2021 23:01:52 +0800 Subject: [PATCH 1675/3726] Update paths.py --- IPython/paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/paths.py b/IPython/paths.py index a2ea5bc1159..f96ec4d36e4 100644 --- a/IPython/paths.py +++ b/IPython/paths.py @@ -72,7 +72,7 @@ def get_ipython_dir() -> str: " using a temp directory.".format(parent)) ipdir = tempfile.mkdtemp() else: - os.makedirs(ipdir) + os.makedirs(ipdir, exist_ok=True) assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not." return ipdir From 69c9dab7c56a46403f311de7d3076aa4ec69f6f6 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Tue, 9 Nov 2021 16:17:45 +0100 Subject: [PATCH 1676/3726] f_locals might not have get --- IPython/core/debugger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 978af307fc3..26aa94c1108 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -415,9 +415,10 @@ def _hidden_predicate(self, frame): if self._predicates["tbhide"]: if frame in (self.curframe, getattr(self, "initial_frame", None)): return False - else: - return self._get_frame_locals(frame).get("__tracebackhide__", False) - + frame_locals = self._get_frame_locals(frame) + if "__tracebackhide__" not in frame_locals: + return False + return frame_locals["__tracebackhide__"] return False def hidden_frames(self, stack): From 4727faae664069510cb6bfcd0c905ce30fc3ca4c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Nov 2021 10:27:54 -0800 Subject: [PATCH 1677/3726] Add Xfail test on 3.9.8 See https://bugs.python.org/issue45738 once fixed (or later if not fixed), we can also catch system error only on the relevant Python versions and that _should_ be ok, but I want to avoid doing it for now. --- IPython/core/tests/test_inputsplitter.py | 2 + IPython/core/tests/test_inputtransformer2.py | 58 +++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 148c5f043e4..0a13fb68c85 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -12,6 +12,7 @@ from IPython.core.inputtransformer import InputTransformer from IPython.core.tests.test_inputtransformer import syntax, syntax_ml from IPython.testing import tools as tt +from IPython.testing.decorators import skipif #----------------------------------------------------------------------------- # Semi-complete examples (also used as tests) @@ -321,6 +322,7 @@ def test_unicode(self): self.isp.push(u'\xc3\xa9') self.isp.push(u"u'\xc3\xa9'") + @skipif(sys.version_info[:3] == (3, 9, 8)) def test_line_continuation(self): """ Test issue #2108.""" isp = self.isp diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index d341ec737ab..2a307d54d13 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -5,11 +5,14 @@ transformations. """ import string +import sys +from textwrap import dedent -from IPython.core import inputtransformer2 as ipt2 -from IPython.core.inputtransformer2 import make_tokens_by_line, _find_assign_op +import pytest -from textwrap import dedent +from IPython.core import inputtransformer2 as ipt2 +from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line +from IPython.testing.decorators import skip_iptest_but_not_pytest MULTILINE_MAGIC = ("""\ a = f() @@ -256,20 +259,45 @@ def __init__(self, s): ) +examples = [ + pytest.param("a = 1", "complete", None), + pytest.param("for a in range(5):", "incomplete", 4), + pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8), + pytest.param("raise = 2", "invalid", None), + pytest.param("a = [1,\n2,", "incomplete", 0), + pytest.param("(\n))", "incomplete", 0), + pytest.param("\\\r\n", "incomplete", 0), + pytest.param("a = '''\n hi", "incomplete", 3), + pytest.param("def a():\n x=1\n global x", "invalid", None), + pytest.param( + "a \\ ", + "invalid", + None, + marks=pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info[:3] == (3, 9, 8), + raises=SystemError, + strict=True, + ), + ), # Nothing allowed after backslash, + pytest.param("1\\\n+2", "complete", None), +] + + +@skip_iptest_but_not_pytest +@pytest.mark.parametrize("code, expected, number", examples) +def test_check_complete_param(code, expected, number): + cc = ipt2.TransformerManager().check_complete + assert cc(code) == (expected, number) + + +@skip_iptest_but_not_pytest +@pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info[:3] == (3, 9, 8), +) def test_check_complete(): cc = ipt2.TransformerManager().check_complete - assert cc("a = 1") == ("complete", None) - assert cc("for a in range(5):") == ("incomplete", 4) - assert cc("for a in range(5):\n if a > 0:") == ("incomplete", 8) - assert cc("raise = 2") == ("invalid", None) - assert cc("a = [1,\n2,") == ("incomplete", 0) - assert cc("(\n))") == ("incomplete", 0) - assert cc("\\\r\n") == ("incomplete", 0) - assert cc("a = '''\n hi") == ("incomplete", 3) - assert cc("def a():\n x=1\n global x") == ("invalid", None) - assert cc("a \\ ") == ("invalid", None) # Nothing allowed after backslash - assert cc("1\\\n+2") == ("complete", None) - assert cc("exit") == ("complete", None) example = dedent(""" if True: From 317b1349689a3fc6794d1e3f585734833f4cee9a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Nov 2021 12:43:37 -0800 Subject: [PATCH 1678/3726] relax coverage target --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 9a798d0c42d..2d3b8bb058c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,6 +6,7 @@ coverage: library: target: auto paths: ['!.*/tests/.*'] + threshold: 0.1% tests: target: auto paths: ['.*/tests/.*'] From 5a2a58eb557894b0fdca22014493399ebb54e40b Mon Sep 17 00:00:00 2001 From: Fernando Perez Date: Wed, 10 Nov 2021 11:21:08 -0800 Subject: [PATCH 1679/3726] Document that `%run` can execute notebooks and ipy scripts. This feature has been there for a long time but was not documented in the %run docstring. This patch only adds documentation, without changing the functionality. --- IPython/core/magics/execution.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index a5cfd5f6a12..f6586d64d99 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -522,7 +522,15 @@ def run(self, parameter_s='', runner=None, %run [-n -i -e -G] [( -t [-N] | -d [-b] | -p [profile options] )] - ( -m mod | file ) [args] + ( -m mod | filename ) [args] + + The filename argument should be either a pure Python script (with + extension `.py`), or a file with custom IPython syntax (such as + magics). If the latter, the file can be either a script with `.ipy` + extension, or a Jupyter notebook with `.ipynb` extension. When running + a Jupyter notebook, the output from print statements and other + displayed objects will appear in the terminal (even matplotlib figures + will open, if a terminal-compliant backend is being used). Parameters after the filename are passed as command-line arguments to the program (put in sys.argv). Then, control returns to IPython's From 86497d9bb8ec6628304323496ec67a9b54015805 Mon Sep 17 00:00:00 2001 From: Fernando Perez Date: Wed, 10 Nov 2021 11:37:01 -0800 Subject: [PATCH 1680/3726] Update `%run` docs to mention parallel with `jupyter run`. --- IPython/core/magics/execution.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index f6586d64d99..e24992ff575 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -530,7 +530,10 @@ def run(self, parameter_s='', runner=None, extension, or a Jupyter notebook with `.ipynb` extension. When running a Jupyter notebook, the output from print statements and other displayed objects will appear in the terminal (even matplotlib figures - will open, if a terminal-compliant backend is being used). + will open, if a terminal-compliant backend is being used). Note that, + at the system command line, the `jupyter run` command offers similar + functionality for executing notebooks (albeit currently with some + differences in supported options). Parameters after the filename are passed as command-line arguments to the program (put in sys.argv). Then, control returns to IPython's From ea376108e2e4434391b0a3698c0be6813540b1c4 Mon Sep 17 00:00:00 2001 From: Fernando Perez Date: Wed, 10 Nov 2021 12:47:53 -0800 Subject: [PATCH 1681/3726] Fix backtics to be RST-formatted, not markdown. --- IPython/core/magics/execution.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index e24992ff575..8a262525f34 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -525,13 +525,13 @@ def run(self, parameter_s='', runner=None, ( -m mod | filename ) [args] The filename argument should be either a pure Python script (with - extension `.py`), or a file with custom IPython syntax (such as - magics). If the latter, the file can be either a script with `.ipy` - extension, or a Jupyter notebook with `.ipynb` extension. When running + extension ``.py``), or a file with custom IPython syntax (such as + magics). If the latter, the file can be either a script with ``.ipy`` + extension, or a Jupyter notebook with ``.ipynb`` extension. When running a Jupyter notebook, the output from print statements and other displayed objects will appear in the terminal (even matplotlib figures will open, if a terminal-compliant backend is being used). Note that, - at the system command line, the `jupyter run` command offers similar + at the system command line, the ``jupyter run`` command offers similar functionality for executing notebooks (albeit currently with some differences in supported options). From dfa6831643798e0eb2c5e752d5e3435e99be7085 Mon Sep 17 00:00:00 2001 From: Kevin Kirsche Date: Fri, 12 Nov 2021 08:26:10 -0500 Subject: [PATCH 1682/3726] Fix DeprecationWarning: There is no current event loop Fix: ``` /Users/redacted/.asdf/installs/python/3.10.0/lib/python3.10/site-packages/IPython/terminal/interactiveshell.py:464: DeprecationWarning: There is no current event loop old_loop = asyncio.get_event_loop() ``` Per https://docs.python.org/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop this method is deprecated and get_running_loop is preferred over this method moving forward. As IPython requires 3.7+ now, this can be updated --- IPython/terminal/interactiveshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index b73a140f751..fd1e0035237 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -505,7 +505,7 @@ def prompt_for_code(self): # while/true inside which will freeze the prompt. try: - old_loop = asyncio.get_event_loop() + old_loop = asyncio.get_running_loop() except RuntimeError: # This happens when the user used `asyncio.run()`. old_loop = None From 29f9992dfa9d2280996835cc41773f802eb793da Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 9 Nov 2021 11:11:21 -0800 Subject: [PATCH 1683/3726] Try to test downstream deps --- .github/workflows/downstream.yml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/downstream.yml diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml new file mode 100644 index 00000000000..168b32e2678 --- /dev/null +++ b/.github/workflows/downstream.yml @@ -0,0 +1,50 @@ +name: Run Downstream tests + +on: + push: + pull_request: + # Run weekly on Monday at 1:23 UTC + schedule: + - cron: '23 1 * * 1' + workflow_dispatch: + + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.9"] + include: + - os: macos-latest + python-version: "3.9" + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Update Python installer + run: | + python -m pip install --upgrade pip setuptools wheel + - name: Install ipykernel + run: | + cd .. + git clone https://github.com/ipython/ipykernel + cd ipykernel + pip install -e .[test] + cd .. + - name: Install and update Python dependencies + run: | + python -m pip install --upgrade -e file://$PWD#egg=ipython[test] + # we must instal IPython after ipykernel to get the right versions. + python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel + python -m pip install --upgrade pytest + - name: pytest + env: + COLUMNS: 120 + run: | + cd ../ipykernel + pytest From 47037d10efddc808ea5909ba3c4c3936d641e7f4 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 19:09:38 +0300 Subject: [PATCH 1684/3726] IPython.testing.tools without Nose --- IPython/testing/tools.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index d1e57306b9d..3dec1ffc634 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -21,15 +21,6 @@ from subprocess import Popen, PIPE from unittest.mock import patch -try: - # These tools are used by parts of the runtime, so we make the nose - # dependency optional at this point. Nose is a hard dependency to run the - # test suite, but NOT to use ipython itself. - import nose.tools as nt - has_nose = True -except ImportError: - has_nose = False - from traitlets.config.loader import Config from IPython.utils.process import get_output_error_code from IPython.utils.text import list_strings @@ -252,8 +243,6 @@ def ipexec_validate(fname, expected_out, expected_err='', None """ - import nose.tools as nt - out, err = ipexec(fname, options, commands) #print 'OUT', out # dbg #print 'ERR', err # dbg @@ -261,12 +250,12 @@ def ipexec_validate(fname, expected_out, expected_err='', # more informative than simply having an empty stdout. if err: if expected_err: - nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines())) + assert err.strip().splitlines() == expected_err.strip().splitlines() else: raise ValueError('Running file %r produced error: %r' % (fname, err)) # If no errors or output on stderr was expected, match stdout - nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines())) + assert out.strip().splitlines() == expected_out.strip().splitlines() class TempFileMixin(unittest.TestCase): @@ -458,10 +447,10 @@ def help_output_test(subcommand=''): """test that `ipython [subcommand] -h` works""" cmd = get_ipython_cmd() + [subcommand, '-h'] out, err, rc = get_output_error_code(cmd) - nt.assert_equal(rc, 0, err) - nt.assert_not_in("Traceback", err) - nt.assert_in("Options", out) - nt.assert_in("--help-all", out) + assert rc == 0, err + assert "Traceback" not in err + assert "Options" in out + assert "--help-all" in out return out, err @@ -469,9 +458,9 @@ def help_all_output_test(subcommand=''): """test that `ipython [subcommand] --help-all` works""" cmd = get_ipython_cmd() + [subcommand, '--help-all'] out, err, rc = get_output_error_code(cmd) - nt.assert_equal(rc, 0, err) - nt.assert_not_in("Traceback", err) - nt.assert_in("Options", out) - nt.assert_in("Class", out) + assert rc == 0, err + assert "Traceback" not in err + assert "Options" in out + assert "Class" in out return out, err From 360df2b6e99e3fabb6a7c1190b40aeba5aa2c997 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 7 Nov 2021 21:04:20 +0300 Subject: [PATCH 1685/3726] Remove iptest and other Nose-dependent code --- .github/workflows/test.yml | 5 - CONTRIBUTING.md | 13 +- IPython/__init__.py | 1 - IPython/core/tests/test_completer.py | 10 +- IPython/core/tests/test_magic.py | 5 +- IPython/core/tests/test_magic_terminal.py | 5 +- IPython/core/tests/test_run.py | 13 +- IPython/external/decorators/__init__.py | 8 - IPython/external/decorators/_decorators.py | 150 ------ .../decorators/_numpy_testing_noseclasses.py | 47 -- IPython/testing/__init__.py | 29 - IPython/testing/__main__.py | 3 - IPython/testing/decorators.py | 73 +-- IPython/testing/iptest.py | 457 ---------------- IPython/testing/iptestcontroller.py | 496 ------------------ IPython/testing/plugin/ipdoctest.py | 310 ----------- IPython/testing/plugin/iptest.py | 18 - IPython/utils/tests/test_path.py | 15 +- appveyor.yml | 6 +- docs/environment.yml | 1 - docs/source/install/install.rst | 5 +- setup.py | 1 - setupbase.py | 1 - tools/release_helper.sh | 2 +- 24 files changed, 44 insertions(+), 1630 deletions(-) delete mode 100644 IPython/external/decorators/__init__.py delete mode 100644 IPython/external/decorators/_decorators.py delete mode 100644 IPython/external/decorators/_numpy_testing_noseclasses.py delete mode 100644 IPython/testing/__main__.py delete mode 100644 IPython/testing/iptest.py delete mode 100644 IPython/testing/iptestcontroller.py delete mode 100755 IPython/testing/plugin/iptest.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a99ac62426..c79db6e0f7f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,11 +38,6 @@ jobs: python -m pip install --upgrade check-manifest pytest-cov anyio - name: Check manifest run: check-manifest - - name: iptest - run: | - cd /tmp && iptest --coverage xml && cd - - cp /tmp/ipy_coverage.xml ./ - cp /tmp/.coverage ./ - name: pytest env: COLUMNS: 120 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c7922cce33..11321a4ca4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,20 +76,15 @@ For more detailed information, see our [GitHub Workflow](https://github.com/ipyt All the tests can be run by using ```shell -iptest +pytest ``` All the tests for a single module (for example **test_alias**) can be run by using the fully qualified path to the module. ```shell -iptest IPython.core.tests.test_alias +pytest IPython/core/tests/test_alias.py ``` -Only a single test (for example **test_alias_lifecycle**) within a single file can be run by adding the specific test after a `:` at the end: +Only a single test (for example **test_alias_lifecycle**) within a single file can be run by adding the specific test after a `::` at the end: ```shell -iptest IPython.core.tests.test_alias:test_alias_lifecycle -``` - -For convenience, the full path to a file can often be used instead of the module path on unix systems. For example we can run all the tests by using -```shell -iptest IPython/core/tests/test_alias.py +pytest IPython/core/tests/test_alias.py::test_alias_lifecycle ``` diff --git a/IPython/__init__.py b/IPython/__init__.py index 7d098bc9ee7..55cb5ef61bd 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -51,7 +51,6 @@ from .terminal.embed import embed from .core.interactiveshell import InteractiveShell -from .testing import test from .utils.sysinfo import sys_info from .utils.frame import extract_module_locals diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index b39818dc80d..61e1d35c1db 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -5,6 +5,7 @@ # Distributed under the terms of the Modified BSD License. import os +import pytest import sys import textwrap import unittest @@ -14,7 +15,6 @@ from traitlets.config.loader import Config from IPython import get_ipython from IPython.core import completer -from IPython.external import decorators from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from IPython.utils.generics import complete_object from IPython.testing import decorators as dec @@ -317,8 +317,8 @@ def test_forward_unicode_completion(self): self.assertEqual(matches, ["\u2164"]) # same as above but explicit. @unittest.skip("now we have a completion for \jmath") - @decorators.knownfailureif( - sys.platform == "win32", "Fails if there is a C:\\j... path" + @pytest.mark.xfail( + sys.platform == "win32", reason="Fails if there is a C:\\j... path" ) def test_no_ascii_back_completion(self): ip = get_ipython() @@ -357,8 +357,8 @@ def test_has_open_quotes4(self): for s in ['""', '""" """', '"hi" "ipython"']: self.assertFalse(completer.has_open_quotes(s)) - @decorators.knownfailureif( - sys.platform == "win32", "abspath completions fail on Windows" + @pytest.mark.xfail( + sys.platform == "win32", reason="abspath completions fail on Windows" ) def test_abspath_file_completions(self): ip = get_ipython() diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 8dd15dcd824..9c08926a3f3 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- -"""Tests for various magic functions. - -Needs to be run by nose (to make ipython session available). -""" +"""Tests for various magic functions.""" import asyncio import io diff --git a/IPython/core/tests/test_magic_terminal.py b/IPython/core/tests/test_magic_terminal.py index 3b53aadf068..cffb7674c8e 100644 --- a/IPython/core/tests/test_magic_terminal.py +++ b/IPython/core/tests/test_magic_terminal.py @@ -1,7 +1,4 @@ -"""Tests for various magic functions specific to the terminal frontend. - -Needs to be run by nose (to make ipython session available). -""" +"""Tests for various magic functions specific to the terminal frontend.""" #----------------------------------------------------------------------------- # Imports diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 742b6b27e4b..a8dfbd0e3c1 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -125,6 +125,9 @@ def doctest_run_option_parser_for_posix(): """ +doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32" + + @dec.skip_if_not_win32 def doctest_run_option_parser_for_windows(): r"""Test option parser in %run (Windows specific). @@ -132,16 +135,19 @@ def doctest_run_option_parser_for_windows(): In Windows, you can't escape ``*` `by backslash: In [1]: %run print_argv.py print\\*.py - ['print\\*.py'] + ['print\\\\*.py'] You can use quote to escape glob: In [2]: %run print_argv.py 'print*.py' - ['print*.py'] + ["'print*.py'"] """ +doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32" + + def doctest_reset_del(): """Test that resetting doesn't cause errors in __del__ methods. @@ -556,7 +562,6 @@ def test_multiprocessing_run(): """ with TemporaryDirectory() as td: mpm = sys.modules.get('__mp_main__') - assert mpm is not None sys.modules['__mp_main__'] = None try: path = pjoin(td, 'test.py') @@ -575,7 +580,7 @@ def test_multiprocessing_run(): finally: sys.modules['__mp_main__'] = mpm -@dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows") + def test_script_tb(): """Test traceback offset in `ipython script.py`""" with TemporaryDirectory() as td: diff --git a/IPython/external/decorators/__init__.py b/IPython/external/decorators/__init__.py deleted file mode 100644 index 1db80edd357..00000000000 --- a/IPython/external/decorators/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -try: - from numpy.testing import KnownFailure, knownfailureif -except ImportError: - from ._decorators import knownfailureif - try: - from ._numpy_testing_noseclasses import KnownFailure - except ImportError: - pass diff --git a/IPython/external/decorators/_decorators.py b/IPython/external/decorators/_decorators.py deleted file mode 100644 index 308652febcc..00000000000 --- a/IPython/external/decorators/_decorators.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Decorators for labeling and modifying behavior of test objects. - -Decorators that merely return a modified version of the original -function object are straightforward. Decorators that return a new -function object need to use -:: - - nose.tools.make_decorator(original_function)(decorator) - -in returning the decorator, in order to preserve meta-data such as -function name, setup and teardown functions and so on - see -``nose.tools`` for more information. - -""" - -# IPython changes: make this work if numpy not available -# Original code: -try: - from ._numpy_testing_noseclasses import KnownFailureTest -except: - pass - -# End IPython changes - - -def skipif(skip_condition, msg=None): - """ - Make function raise SkipTest exception if a given condition is true. - - If the condition is a callable, it is used at runtime to dynamically - make the decision. This is useful for tests that may require costly - imports, to delay the cost until the test suite is actually executed. - - Parameters - ---------- - skip_condition : bool or callable - Flag to determine whether to skip the decorated test. - msg : str, optional - Message to give on raising a SkipTest exception. Default is None. - - Returns - ------- - decorator : function - Decorator which, when applied to a function, causes SkipTest - to be raised when `skip_condition` is True, and the function - to be called normally otherwise. - - Notes - ----- - The decorator itself is decorated with the ``nose.tools.make_decorator`` - function in order to transmit function name, and various other metadata. - - """ - - def skip_decorator(f): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. - import nose - - # Allow for both boolean or callable skip conditions. - if callable(skip_condition): - skip_val = lambda : skip_condition() - else: - skip_val = lambda : skip_condition - - def get_msg(func,msg=None): - """Skip message with information about function being skipped.""" - if msg is None: - out = 'Test skipped due to test condition' - else: - out = '\n'+msg - - return "Skipping test: %s%s" % (func.__name__,out) - - # We need to define *two* skippers because Python doesn't allow both - # return with value and yield inside the same function. - def skipper_func(*args, **kwargs): - """Skipper for normal test functions.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - return f(*args, **kwargs) - - def skipper_gen(*args, **kwargs): - """Skipper for test generators.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - for x in f(*args, **kwargs): - yield x - - # Choose the right skipper to use when building the actual decorator. - if nose.util.isgenerator(f): - skipper = skipper_gen - else: - skipper = skipper_func - - return nose.tools.make_decorator(f)(skipper) - - return skip_decorator - -def knownfailureif(fail_condition, msg=None): - """ - Make function raise KnownFailureTest exception if given condition is true. - - Parameters - ---------- - fail_condition : bool - Flag to determine whether to mark the decorated test as a known - failure (if True) or not (if False). - msg : str, optional - Message to give on raising a KnownFailureTest exception. - Default is None. - - Returns - ------- - decorator : function - Decorator, which, when applied to a function, causes KnownFailureTest to - be raised when `fail_condition` is True and the test fails. - - Notes - ----- - The decorator itself is decorated with the ``nose.tools.make_decorator`` - function in order to transmit function name, and various other metadata. - - """ - if msg is None: - msg = 'Test skipped due to known failure' - - def knownfail_decorator(f): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. - import nose - - try: - from pytest import xfail - except ImportError: - - def xfail(msg): - raise KnownFailureTest(msg) - - def knownfailer(*args, **kwargs): - if fail_condition: - xfail(msg) - else: - return f(*args, **kwargs) - return nose.tools.make_decorator(f)(knownfailer) - - return knownfail_decorator diff --git a/IPython/external/decorators/_numpy_testing_noseclasses.py b/IPython/external/decorators/_numpy_testing_noseclasses.py deleted file mode 100644 index 7a4360cf5b6..00000000000 --- a/IPython/external/decorators/_numpy_testing_noseclasses.py +++ /dev/null @@ -1,47 +0,0 @@ -# IPython: modified copy of numpy.testing.noseclasses, so -# IPython.external._decorators works without numpy being installed. - -# These classes implement a "known failure" error class. - -import os - -from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin - - -try: - import pytest - - KnownFailureTest = pytest.xfail.Exception -except ImportError: - - class KnownFailureTest(Exception): - """Raise this exception to mark a test as a known failing test.""" - - -class KnownFailure(ErrorClassPlugin): - '''Plugin that installs a KNOWNFAIL error class for the - KnownFailureClass exception. When KnownFailureTest is raised, - the exception will be logged in the knownfail attribute of the - result, 'K' or 'KNOWNFAIL' (verbose) will be output, and the - exception will not be counted as an error or failure.''' - enabled = True - knownfail = ErrorClass(KnownFailureTest, - label='KNOWNFAIL', - isfailure=False) - - def options(self, parser, env=os.environ): - env_opt = 'NOSE_WITHOUT_KNOWNFAIL' - parser.add_option('--no-knownfail', action='store_true', - dest='noKnownFail', default=env.get(env_opt, False), - help='Disable special handling of KnownFailureTest ' - 'exceptions') - - def configure(self, options, conf): - if not self.can_configure: - return - self.conf = conf - disable = getattr(options, 'noKnownFail', False) - if disable: - self.enabled = False - - diff --git a/IPython/testing/__init__.py b/IPython/testing/__init__.py index 552608792d9..8fcd65ea41a 100644 --- a/IPython/testing/__init__.py +++ b/IPython/testing/__init__.py @@ -11,31 +11,6 @@ import os -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- - -# User-level entry point for testing -def test(**kwargs): - """Run the entire IPython test suite. - - Any of the options for run_iptestall() may be passed as keyword arguments. - - For example:: - - IPython.test(testgroups=['lib', 'config', 'utils'], fast=2) - - will run those three sections of the test suite, using two processes. - """ - - # Do the import internally, so that this function doesn't increase total - # import time - from .iptestcontroller import run_iptestall, default_options - options = default_options() - for name, val in kwargs.items(): - setattr(options, name, val) - run_iptestall(options) - #----------------------------------------------------------------------------- # Constants #----------------------------------------------------------------------------- @@ -43,7 +18,3 @@ def test(**kwargs): # We scale all timeouts via this factor, slow machines can increase it IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv( 'IPYTHON_TESTING_TIMEOUT_SCALE', 1)) - -# So nose doesn't try to run this as a test itself and we end up with an -# infinite test loop -test.__test__ = False diff --git a/IPython/testing/__main__.py b/IPython/testing/__main__.py deleted file mode 100644 index 4b0bb8ba9ca..00000000000 --- a/IPython/testing/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -if __name__ == '__main__': - from IPython.testing import iptestcontroller - iptestcontroller.main() diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 3ea101ce489..74af7c1c83d 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -44,11 +44,6 @@ # Expose the unittest-driven decorators from .ipunittest import ipdoctest, ipdocstring -# Grab the numpy-specific decorators which we keep in a file that we -# occasionally update from upstream: decorators.py is a copy of -# numpy.testing.decorators, we expose all of it here. -from IPython.external.decorators import knownfailureif - #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- @@ -165,11 +160,8 @@ def skip_iptest_but_not_pytest(f): return f -# Inspired by numpy's skipif, but uses the full apply_wrapper utility to -# preserve function metadata better and allows the skip condition to be a -# callable. def skipif(skip_condition, msg=None): - ''' Make function raise SkipTest exception if skip_condition is true + """Make function raise SkipTest exception if skip_condition is true Parameters ---------- @@ -188,57 +180,15 @@ def skipif(skip_condition, msg=None): Decorator, which, when applied to a function, causes SkipTest to be raised when the skip_condition was True, and the function to be called normally otherwise. + """ + if msg is None: + msg = "Test skipped due to test condition." - Notes - ----- - You will see from the code that we had to further decorate the - decorator with the nose.tools.make_decorator function in order to - transmit function name, and various other metadata. - ''' - - def skip_decorator(f): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. - import nose - - # Allow for both boolean or callable skip conditions. - if callable(skip_condition): - skip_val = skip_condition - else: - skip_val = lambda : skip_condition - - def get_msg(func,msg=None): - """Skip message with information about function being skipped.""" - if msg is None: out = 'Test skipped due to test condition.' - else: out = msg - return "Skipping test: %s. %s" % (func.__name__,out) - - # We need to define *two* skippers because Python doesn't allow both - # return with value and yield inside the same function. - def skipper_func(*args, **kwargs): - """Skipper for normal test functions.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - return f(*args, **kwargs) - - def skipper_gen(*args, **kwargs): - """Skipper for test generators.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - for x in f(*args, **kwargs): - yield x - - # Choose the right skipper to use when building the actual generator. - if nose.util.isgenerator(f): - skipper = skipper_gen - else: - skipper = skipper_func + import pytest - return nose.tools.make_decorator(f)(skipper) + assert isinstance(skip_condition, bool) + return pytest.mark.skipif(skip_condition, reason=msg) - return skip_decorator # A version with the condition set to true, common case just to attach a message # to a skip decorator @@ -265,12 +215,7 @@ def skip(msg=None): def onlyif(condition, msg): """The reverse from skipif, see skipif for details.""" - if callable(condition): - skip_condition = lambda : not condition() - else: - skip_condition = lambda : not condition - - return skipif(skip_condition, msg) + return skipif(not condition, msg) #----------------------------------------------------------------------------- # Utility functions for decorators @@ -351,8 +296,6 @@ def skip_file_no_x11(name): skipif_not_sympy = skip_without('sympy') -skip_known_failure = knownfailureif(True,'This test is known to fail') - # A null 'decorator', useful to make more readable code that needs to pick # between different decorators based on OS or other conditions null_deco = lambda f: f diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py deleted file mode 100644 index 8b7e34cbb3e..00000000000 --- a/IPython/testing/iptest.py +++ /dev/null @@ -1,457 +0,0 @@ -# -*- coding: utf-8 -*- -"""IPython Test Suite Runner. - -This module provides a main entry point to a user script to test IPython -itself from the command line. There are two ways of running this script: - -1. With the syntax `iptest all`. This runs our entire test suite by - calling this script (with different arguments) recursively. This - causes modules and package to be tested in different processes, using nose - or trial where appropriate. -2. With the regular nose syntax, like `iptest IPython -- -vvs`. In this form - the script simply calls nose, but with special command line flags and - plugins loaded. Options after `--` are passed to nose. - -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -import glob -from io import BytesIO -import os -import os.path as path -import sys -from threading import Thread, Lock, Event -import warnings - -import nose.plugins.builtin -from nose.plugins.xunit import Xunit -from nose import SkipTest -from nose.core import TestProgram -from nose.plugins import Plugin -from nose.util import safe_str - -from IPython import version_info -from IPython.utils.py3compat import decode -from IPython.utils.importstring import import_item -from IPython.testing.plugin.ipdoctest import IPythonDoctest -from IPython.external.decorators import KnownFailure, knownfailureif - -pjoin = path.join - - -# Enable printing all warnings raise by IPython's modules -warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*') -warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*') -warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*') -warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*') - -warnings.filterwarnings('error', message='.*apply_wrapper.*', category=DeprecationWarning, module='.*') -warnings.filterwarnings('error', message='.*make_label_dec', category=DeprecationWarning, module='.*') -warnings.filterwarnings('error', message='.*decorated_dummy.*', category=DeprecationWarning, module='.*') -warnings.filterwarnings('error', message='.*skip_file_no_x11.*', category=DeprecationWarning, module='.*') -warnings.filterwarnings('error', message='.*onlyif_any_cmd_exists.*', category=DeprecationWarning, module='.*') - -warnings.filterwarnings('error', message='.*disable_gui.*', category=DeprecationWarning, module='.*') - -warnings.filterwarnings('error', message='.*ExceptionColors global is deprecated.*', category=DeprecationWarning, module='.*') -warnings.filterwarnings('error', message='.*IPython.core.display.*', category=DeprecationWarning, module='.*') - -# Jedi older versions -warnings.filterwarnings( - 'error', message='.*elementwise != comparison failed and.*', category=FutureWarning, module='.*') - -if version_info < (6,): - # nose.tools renames all things from `camelCase` to `snake_case` which raise an - # warning with the runner they also import from standard import library. (as of Dec 2015) - # Ignore, let's revisit that in a couple of years for IPython 6. - warnings.filterwarnings( - 'ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*') - -if version_info < (8,): - warnings.filterwarnings('ignore', message='.*Completer.complete.*', - category=PendingDeprecationWarning, module='.*') -else: - warnings.warn( - 'Completer.complete was pending deprecation and should be changed to Deprecated', FutureWarning) - - - -# ------------------------------------------------------------------------------ -# Monkeypatch Xunit to count known failures as skipped. -# ------------------------------------------------------------------------------ -def monkeypatch_xunit(): - try: - dec.knownfailureif(True)(lambda: None)() - except Exception as e: - KnownFailureTest = type(e) - - def addError(self, test, err, capt=None): - if issubclass(err[0], KnownFailureTest): - err = (SkipTest,) + err[1:] - return self.orig_addError(test, err, capt) - - Xunit.orig_addError = Xunit.addError - Xunit.addError = addError - -#----------------------------------------------------------------------------- -# Check which dependencies are installed and greater than minimum version. -#----------------------------------------------------------------------------- -def extract_version(mod): - return mod.__version__ - -def test_for(item, min_version=None, callback=extract_version): - """Test to see if item is importable, and optionally check against a minimum - version. - - If min_version is given, the default behavior is to check against the - `__version__` attribute of the item, but specifying `callback` allows you to - extract the value you are interested in. e.g:: - - In [1]: import sys - - In [2]: from IPython.testing.iptest import test_for - - In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info) - Out[3]: True - - """ - try: - check = import_item(item) - except (ImportError, RuntimeError): - # GTK reports Runtime error if it can't be initialized even if it's - # importable. - return False - else: - if min_version: - if callback: - # extra processing step to get version to compare - check = callback(check) - - return check >= min_version - else: - return True - -# Global dict where we can store information on what we have and what we don't -# have available at test run time -have = {'matplotlib': test_for('matplotlib'), - 'pygments': test_for('pygments'), - } - -#----------------------------------------------------------------------------- -# Test suite definitions -#----------------------------------------------------------------------------- - -test_group_names = ['core', - 'extensions', 'lib', 'terminal', 'testing', 'utils', - ] - -class TestSection(object): - def __init__(self, name, includes): - self.name = name - self.includes = includes - self.excludes = [] - self.dependencies = [] - self.enabled = True - - def exclude(self, module): - if not module.startswith('IPython'): - module = self.includes[0] + "." + module - self.excludes.append(module.replace('.', os.sep)) - - def requires(self, *packages): - self.dependencies.extend(packages) - - @property - def will_run(self): - return self.enabled and all(have[p] for p in self.dependencies) - -# Name -> (include, exclude, dependencies_met) -test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names} - - -# Exclusions and dependencies -# --------------------------- - -# core: -sec = test_sections['core'] -if not have['matplotlib']: - sec.exclude('pylabtools'), - sec.exclude('tests.test_pylabtools') - -# lib: -sec = test_sections['lib'] -sec.exclude('tests.test_latextools') -#sec.exclude('kernel') -if not have['pygments']: - sec.exclude('tests.test_lexers') -# We do this unconditionally, so that the test suite doesn't import -# gtk, changing the default encoding and masking some unicode bugs. -sec.exclude('inputhookgtk') -# We also do this unconditionally, because wx can interfere with Unix signals. -# There are currently no tests for it anyway. -sec.exclude('inputhookwx') -# Testing inputhook will need a lot of thought, to figure out -# how to have tests that don't lock up with the gui event -# loops in the picture -sec.exclude('inputhook') - -# testing: -sec = test_sections['testing'] -# These have to be skipped on win32 because they use echo, rm, cd, etc. -# See ticket https://github.com/ipython/ipython/issues/87 -if sys.platform == 'win32': - sec.exclude('plugin.test_exampleip') - sec.exclude('plugin.dtexample') - -# don't run jupyter_console tests found via shim -test_sections['terminal'].exclude('console') - -# extensions: -sec = test_sections['extensions'] -# autoreload does some strange stuff, so move it to its own test section -sec.exclude('autoreload') -sec.exclude('tests.test_autoreload') -test_sections['autoreload'] = TestSection('autoreload', - ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload']) -test_group_names.append('autoreload') - - -#----------------------------------------------------------------------------- -# Functions and classes -#----------------------------------------------------------------------------- - -def check_exclusions_exist(): - from IPython.paths import get_ipython_package_dir - from warnings import warn - parent = os.path.dirname(get_ipython_package_dir()) - for sec in test_sections: - for pattern in sec.exclusions: - fullpath = pjoin(parent, pattern) - if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'): - warn("Excluding nonexistent file: %r" % pattern) - - -class ExclusionPlugin(Plugin): - """A nose plugin to effect our exclusions of files and directories. - """ - name = 'exclusions' - score = 3000 # Should come before any other plugins - - def __init__(self, exclude_patterns=None): - """ - Parameters - ---------- - - exclude_patterns : sequence of strings, optional - Filenames containing these patterns (as raw strings, not as regular - expressions) are excluded from the tests. - """ - self.exclude_patterns = exclude_patterns or [] - super(ExclusionPlugin, self).__init__() - - def options(self, parser, env=os.environ): - Plugin.options(self, parser, env) - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Override nose trying to disable plugin. - self.enabled = True - - def wantFile(self, filename): - """Return whether the given filename should be scanned for tests. - """ - if any(pat in filename for pat in self.exclude_patterns): - return False - return None - - def wantDirectory(self, directory): - """Return whether the given directory should be scanned for tests. - """ - if any(pat in directory for pat in self.exclude_patterns): - return False - return None - - -class StreamCapturer(Thread): - daemon = True # Don't hang if main thread crashes - started = False - def __init__(self, echo=False): - super(StreamCapturer, self).__init__() - self.echo = echo - self.streams = [] - self.buffer = BytesIO() - self.readfd, self.writefd = os.pipe() - self.buffer_lock = Lock() - self.stop = Event() - - def run(self): - self.started = True - - while not self.stop.is_set(): - chunk = os.read(self.readfd, 1024) - - with self.buffer_lock: - self.buffer.write(chunk) - if self.echo: - sys.stdout.write(decode(chunk)) - - os.close(self.readfd) - os.close(self.writefd) - - def reset_buffer(self): - with self.buffer_lock: - self.buffer.truncate(0) - self.buffer.seek(0) - - def get_buffer(self): - with self.buffer_lock: - return self.buffer.getvalue() - - def ensure_started(self): - if not self.started: - self.start() - - def halt(self): - """Safely stop the thread.""" - if not self.started: - return - - self.stop.set() - os.write(self.writefd, b'\0') # Ensure we're not locked in a read() - self.join() - -class SubprocessStreamCapturePlugin(Plugin): - name='subprocstreams' - def __init__(self): - Plugin.__init__(self) - self.stream_capturer = StreamCapturer() - self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture') - # This is ugly, but distant parts of the test machinery need to be able - # to redirect streams, so we make the object globally accessible. - nose.iptest_stdstreams_fileno = self.get_write_fileno - - def get_write_fileno(self): - if self.destination == 'capture': - self.stream_capturer.ensure_started() - return self.stream_capturer.writefd - elif self.destination == 'discard': - return os.open(os.devnull, os.O_WRONLY) - else: - return sys.__stdout__.fileno() - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Override nose trying to disable plugin. - if self.destination == 'capture': - self.enabled = True - - def startTest(self, test): - # Reset log capture - self.stream_capturer.reset_buffer() - - def formatFailure(self, test, err): - # Show output - ec, ev, tb = err - captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') - if captured.strip(): - ev = safe_str(ev) - out = [ev, '>> begin captured subprocess output <<', - captured, - '>> end captured subprocess output <<'] - return ec, '\n'.join(out), tb - - return err - - formatError = formatFailure - - def finalize(self, result): - self.stream_capturer.halt() - - -def run_iptest(): - """Run the IPython test suite using nose. - - This function is called when this script is **not** called with the form - `iptest all`. It simply calls nose with appropriate command line flags - and accepts all of the standard nose arguments. - """ - # Apply our monkeypatch to Xunit - if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'): - monkeypatch_xunit() - - arg1 = sys.argv[1] - if arg1.startswith('IPython/'): - if arg1.endswith('.py'): - arg1 = arg1[:-3] - sys.argv[1] = arg1.replace('/', '.') - - arg1 = sys.argv[1] - if arg1 in test_sections: - section = test_sections[arg1] - sys.argv[1:2] = section.includes - elif arg1.startswith('IPython.') and arg1[8:] in test_sections: - section = test_sections[arg1[8:]] - sys.argv[1:2] = section.includes - else: - section = TestSection(arg1, includes=[arg1]) - - - argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks - # We add --exe because of setuptools' imbecility (it - # blindly does chmod +x on ALL files). Nose does the - # right thing and it tries to avoid executables, - # setuptools unfortunately forces our hand here. This - # has been discussed on the distutils list and the - # setuptools devs refuse to fix this problem! - '--exe', - ] - if '-a' not in argv and '-A' not in argv: - argv = argv + ['-a', '!crash'] - - if nose.__version__ >= '0.11': - # I don't fully understand why we need this one, but depending on what - # directory the test suite is run from, if we don't give it, 0 tests - # get run. Specifically, if the test suite is run from the source dir - # with an argument (like 'iptest.py IPython.core', 0 tests are run, - # even if the same call done in this directory works fine). It appears - # that if the requested package is in the current dir, nose bails early - # by default. Since it's otherwise harmless, leave it in by default - # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it. - argv.append('--traverse-namespace') - - plugins = [ ExclusionPlugin(section.excludes), KnownFailure(), - SubprocessStreamCapturePlugin() ] - - # we still have some vestigial doctests in core - if (section.name.startswith(('core', 'IPython.core', 'IPython.utils'))): - plugins.append(IPythonDoctest()) - argv.extend([ - '--with-ipdoctest', - '--ipdoctest-tests', - '--ipdoctest-extension=txt', - ]) - - - # Use working directory set by parent process (see iptestcontroller) - if 'IPTEST_WORKING_DIR' in os.environ: - os.chdir(os.environ['IPTEST_WORKING_DIR']) - - # We need a global ipython running in this process, but the special - # in-process group spawns its own IPython kernels, so for *that* group we - # must avoid also opening the global one (otherwise there's a conflict of - # singletons). Ultimately the solution to this problem is to refactor our - # assumptions about what needs to be a singleton and what doesn't (app - # objects should, individual shells shouldn't). But for now, this - # workaround allows the test suite for the inprocess module to complete. - if 'kernel.inprocess' not in section.name: - from IPython.testing import globalipapp - globalipapp.start_ipython() - - # Now nose can run - TestProgram(argv=argv, addplugins=plugins) - -if __name__ == '__main__': - run_iptest() diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py deleted file mode 100644 index e50ab862ff3..00000000000 --- a/IPython/testing/iptestcontroller.py +++ /dev/null @@ -1,496 +0,0 @@ -# -*- coding: utf-8 -*- -"""IPython Test Process Controller - -This module runs one or more subprocesses which will actually run the IPython -test suite. - -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -import argparse -import multiprocessing.pool -import os -import stat -import shutil -import signal -import sys -import subprocess -import time - -from .iptest import ( - have, test_group_names as py_test_group_names, test_sections, StreamCapturer, -) -from IPython.utils.path import compress_user -from IPython.utils.py3compat import decode -from IPython.utils.sysinfo import get_sys_info -from IPython.utils.tempdir import TemporaryDirectory -from pathlib import Path -from typing import Dict - -class TestController: - """Run tests in a subprocess - """ - #: str, IPython test suite to be executed. - section = None - #: list, command line arguments to be executed - cmd = None - #: dict, extra environment variables to set for the subprocess - env: Dict[str, str] = {} - #: list, TemporaryDirectory instances to clear up when the process finishes - dirs = None - #: subprocess.Popen instance - process = None - #: str, process stdout+stderr - stdout = None - - def __init__(self): - self.cmd = [] - self.env = {} - self.dirs = [] - - def setUp(self): - """Create temporary directories etc. - - This is only called when we know the test group will be run. Things - created here may be cleaned up by self.cleanup(). - """ - pass - - def launch(self, buffer_output=False, capture_output=False): - # print('*** ENV:', self.env) # dbg - # print('*** CMD:', self.cmd) # dbg - env = os.environ.copy() - env.update(self.env) - if buffer_output: - capture_output = True - self.stdout_capturer = c = StreamCapturer(echo=not buffer_output) - c.start() - stdout = c.writefd if capture_output else None - stderr = subprocess.STDOUT if capture_output else None - for k, v in env.items(): - assert isinstance(v, str), f"env[{repr(k)}] is not a str: {v}" - self.process = subprocess.Popen(self.cmd, stdout=stdout, - stderr=stderr, env=env) - - def wait(self): - self.process.wait() - self.stdout_capturer.halt() - self.stdout = self.stdout_capturer.get_buffer() - return self.process.returncode - - def cleanup_process(self): - """Cleanup on exit by killing any leftover processes.""" - subp = self.process - if subp is None or (subp.poll() is not None): - return # Process doesn't exist, or is already dead. - - try: - print('Cleaning up stale PID: %d' % subp.pid) - subp.kill() - except: # (OSError, WindowsError) ? - # This is just a best effort, if we fail or the process was - # really gone, ignore it. - pass - else: - for i in range(10): - if subp.poll() is None: - time.sleep(0.1) - else: - break - - if subp.poll() is None: - # The process did not die... - print('... failed. Manual cleanup may be required.') - - def cleanup(self): - "Kill process if it's still alive, and clean up temporary directories" - self.cleanup_process() - for td in self.dirs: - td.cleanup() - - __del__ = cleanup - - -class PyTestController(TestController): - """Run Python tests using IPython.testing.iptest""" - #: str, Python command to execute in subprocess - pycmd = None - - def __init__(self, section, options): - """Create new test runner.""" - TestController.__init__(self) - self.section = section - # pycmd is put into cmd[2] in PyTestController.launch() - self.cmd = [sys.executable, '-c', None, section] - self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()" - self.options = options - - def setup(self): - ipydir = TemporaryDirectory() - self.dirs.append(ipydir) - self.env['IPYTHONDIR'] = ipydir.name - self.workingdir = workingdir = TemporaryDirectory() - self.dirs.append(workingdir) - self.env['IPTEST_WORKING_DIR'] = workingdir.name - # This means we won't get odd effects from our own matplotlib config - self.env['MPLCONFIGDIR'] = workingdir.name - # For security reasons (http://bugs.python.org/issue16202), use - # a temporary directory to which other users have no access. - self.env['TMPDIR'] = workingdir.name - - # Add a non-accessible directory to PATH (see gh-7053) - noaccess = Path(self.workingdir.name) / "_no_access_" - self.noaccess = noaccess.resolve() - - noaccess.mkdir(0, exist_ok=True) - - PATH = os.environ.get('PATH', '') - if PATH: - PATH = noaccess / PATH - else: - PATH = noaccess - self.env["PATH"] = str(PATH) - - # From options: - if self.options.xunit: - self.add_xunit() - if self.options.coverage: - self.add_coverage() - self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams - self.cmd.extend(self.options.extra_args) - - def cleanup(self): - """ - Make the non-accessible directory created in setup() accessible - again, otherwise deleting the workingdir will fail. - """ - self.noaccess.chmod(stat.S_IRWXU) - TestController.cleanup(self) - - @property - def will_run(self): - try: - return test_sections[self.section].will_run - except KeyError: - return True - - def add_xunit(self): - xunit_file = Path(f"{self.section}.xunit.xml") - self.cmd.extend(["--with-xunit", "--xunit-file", xunit_file.absolute()]) - - def add_coverage(self): - try: - sources = test_sections[self.section].includes - except KeyError: - sources = ["IPython"] - - coverage_rc = ( - "[run]\n" "data_file = {data_file}\n" "source =\n" " {source}\n" - ).format( - data_file=Path(f".coverage.{self.section}").absolute(), - source="\n ".join(sources), - ) - config_file: Path = Path(self.workingdir.name) / ".coveragerc" - config_file.touch(exist_ok=True) - config_file.write_text(coverage_rc) - - self.env["COVERAGE_PROCESS_START"] = str(config_file.resolve()) - self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd - - def launch(self, buffer_output=False): - self.cmd[2] = self.pycmd - super(PyTestController, self).launch(buffer_output=buffer_output) - - -def prepare_controllers(options): - """Returns two lists of TestController instances, those to run, and those - not to run.""" - testgroups = options.testgroups - if not testgroups: - testgroups = py_test_group_names - - controllers = [PyTestController(name, options) for name in testgroups] - - to_run = [c for c in controllers if c.will_run] - not_run = [c for c in controllers if not c.will_run] - return to_run, not_run - -def do_run(controller, buffer_output=True): - """Setup and run a test controller. - - If buffer_output is True, no output is displayed, to avoid it appearing - interleaved. In this case, the caller is responsible for displaying test - output on failure. - - Returns - ------- - controller : TestController - The same controller as passed in, as a convenience for using map() type - APIs. - exitcode : int - The exit code of the test subprocess. Non-zero indicates failure. - """ - try: - try: - controller.setup() - controller.launch(buffer_output=buffer_output) - except Exception: - import traceback - traceback.print_exc() - return controller, 1 # signal failure - - exitcode = controller.wait() - return controller, exitcode - - except KeyboardInterrupt: - return controller, -signal.SIGINT - finally: - controller.cleanup() - -def report(): - """Return a string with a summary report of test-related variables.""" - inf = get_sys_info() - out = [] - def _add(name, value): - out.append((name, value)) - - _add('IPython version', inf['ipython_version']) - _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source'])) - _add('IPython package', compress_user(inf['ipython_path'])) - _add('Python version', inf['sys_version'].replace('\n','')) - _add('sys.executable', compress_user(inf['sys_executable'])) - _add('Platform', inf['platform']) - - width = max(len(n) for (n,v) in out) - out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out] - - avail = [] - not_avail = [] - - for k, is_avail in have.items(): - if is_avail: - avail.append(k) - else: - not_avail.append(k) - - if avail: - out.append('\nTools and libraries available at test time:\n') - avail.sort() - out.append(' ' + ' '.join(avail)+'\n') - - if not_avail: - out.append('\nTools and libraries NOT available at test time:\n') - not_avail.sort() - out.append(' ' + ' '.join(not_avail)+'\n') - - return ''.join(out) - -def run_iptestall(options): - """Run the entire IPython test suite by calling nose and trial. - - This function constructs :class:`IPTester` instances for all IPython - modules and package and then runs each of them. This causes the modules - and packages of IPython to be tested each in their own subprocess using - nose. - - Parameters - ---------- - - All parameters are passed as attributes of the options object. - - testgroups : list of str - Run only these sections of the test suite. If empty, run all the available - sections. - - fast : int or None - Run the test suite in parallel, using n simultaneous processes. If None - is passed, one process is used per CPU core. Default 1 (i.e. sequential) - - inc_slow : bool - Include slow tests. By default, these tests aren't run. - - url : unicode - Address:port to use when running the JS tests. - - xunit : bool - Produce Xunit XML output. This is written to multiple foo.xunit.xml files. - - coverage : bool or str - Measure code coverage from tests. True will store the raw coverage data, - or pass 'html' or 'xml' to get reports. - - extra_args : list - Extra arguments to pass to the test subprocesses, e.g. '-v' - """ - to_run, not_run = prepare_controllers(options) - - def justify(ltext, rtext, width=70, fill='-'): - ltext += ' ' - rtext = (' ' + rtext).rjust(width - len(ltext), fill) - return ltext + rtext - - # Run all test runners, tracking execution time - failed = [] - t_start = time.time() - - print() - if options.fast == 1: - # This actually means sequential, i.e. with 1 job - for controller in to_run: - print('Test group:', controller.section) - sys.stdout.flush() # Show in correct order when output is piped - controller, res = do_run(controller, buffer_output=False) - if res: - failed.append(controller) - if res == -signal.SIGINT: - print("Interrupted") - break - print() - - else: - # Run tests concurrently - try: - pool = multiprocessing.pool.ThreadPool(options.fast) - for (controller, res) in pool.imap_unordered(do_run, to_run): - res_string = 'OK' if res == 0 else 'FAILED' - print(justify('Test group: ' + controller.section, res_string)) - if res: - print(decode(controller.stdout)) - failed.append(controller) - if res == -signal.SIGINT: - print("Interrupted") - break - except KeyboardInterrupt: - return - - for controller in not_run: - print(justify('Test group: ' + controller.section, 'NOT RUN')) - - t_end = time.time() - t_tests = t_end - t_start - nrunners = len(to_run) - nfail = len(failed) - # summarize results - print('_'*70) - print('Test suite completed for system with the following information:') - print(report()) - took = "Took %.3fs." % t_tests - print('Status: ', end='') - if not failed: - print('OK (%d test groups).' % nrunners, took) - else: - # If anything went wrong, point out what command to rerun manually to - # see the actual errors and individual summary - failed_sections = [c.section for c in failed] - print('ERROR - {} out of {} test groups failed ({}).'.format(nfail, - nrunners, ', '.join(failed_sections)), took) - print() - print('You may wish to rerun these, with:') - print(' iptest', *failed_sections) - print() - - if options.coverage: - from coverage import coverage, CoverageException - cov = coverage(data_file='.coverage') - cov.combine() - cov.save() - - # Coverage HTML report - if options.coverage == 'html': - html_dir = 'ipy_htmlcov' - shutil.rmtree(html_dir, ignore_errors=True) - print("Writing HTML coverage report to %s/ ... " % html_dir, end="") - sys.stdout.flush() - - # Custom HTML reporter to clean up module names. - from coverage.html import HtmlReporter - class CustomHtmlReporter(HtmlReporter): - def find_code_units(self, morfs): - super(CustomHtmlReporter, self).find_code_units(morfs) - for cu in self.code_units: - nameparts = cu.name.split(os.sep) - if 'IPython' not in nameparts: - continue - ix = nameparts.index('IPython') - cu.name = '.'.join(nameparts[ix:]) - - # Reimplement the html_report method with our custom reporter - cov.get_data() - cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir, - html_title='IPython test coverage', - ) - reporter = CustomHtmlReporter(cov, cov.config) - reporter.report(None) - print('done.') - - # Coverage XML report - elif options.coverage == 'xml': - try: - cov.xml_report(outfile='ipy_coverage.xml') - except CoverageException as e: - print('Generating coverage report failed. Are you running javascript tests only?') - import traceback - traceback.print_exc() - - if failed: - # Ensure that our exit code indicates failure - sys.exit(1) - -argparser = argparse.ArgumentParser(description='Run IPython test suite') -argparser.add_argument('testgroups', nargs='*', - help='Run specified groups of tests. If omitted, run ' - 'all tests.') -argparser.add_argument('--all', action='store_true', - help='Include slow tests not run by default.') -argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int, - help='Run test sections in parallel. This starts as many ' - 'processes as you have cores, or you can specify a number.') -argparser.add_argument('--xunit', action='store_true', - help='Produce Xunit XML results') -argparser.add_argument('--coverage', nargs='?', const=True, default=False, - help="Measure test coverage. Specify 'html' or " - "'xml' to get reports.") -argparser.add_argument('--subproc-streams', default='capture', - help="What to do with stdout/stderr from subprocesses. " - "'capture' (default), 'show' and 'discard' are the options.") - -def default_options(): - """Get an argparse Namespace object with the default arguments, to pass to - :func:`run_iptestall`. - """ - options = argparser.parse_args([]) - options.extra_args = [] - return options - -def main(): - # iptest doesn't work correctly if the working directory is the - # root of the IPython source tree. Tell the user to avoid - # frustration. - main_file = Path.cwd() / Path("IPython/testing/__main__.py") - - if main_file.exists(): - print("Don't run iptest from the IPython source directory", file=sys.stderr) - sys.exit(1) - # Arguments after -- should be passed through to nose. Argparse treats - # everything after -- as regular positional arguments, so we separate them - # first. - try: - ix = sys.argv.index('--') - except ValueError: - to_parse = sys.argv[1:] - extra_args = [] - else: - to_parse = sys.argv[1:ix] - extra_args = sys.argv[ix+1:] - - options = argparser.parse_args(to_parse) - options.extra_args = extra_args - - run_iptestall(options) - - -if __name__ == '__main__': - main() diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index fdc8822b853..784760f81cf 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -19,35 +19,14 @@ # Module imports # From the standard library -import builtins as builtin_mod import doctest import inspect import logging import os import re -import sys -from importlib import import_module -from io import StringIO from testpath import modified_env -from inspect import getmodule - -from pathlib import Path, PurePath - -# We are overriding the default doctest runner, so we need to import a few -# things from doctest directly -from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, - _unittest_reportflags, DocTestRunner, - _extract_future_flags, pdb, _OutputRedirectingPdb, - _exception_traceback, - linecache) - -# Third-party modules - -from nose.plugins import doctests, Plugin -from nose.util import anyp, tolist - #----------------------------------------------------------------------------- # Module globals and other constants #----------------------------------------------------------------------------- @@ -195,129 +174,6 @@ def check_output(self, want, got, optionflags): return ret -class DocTestCase(doctests.DocTestCase): - """Proxy for DocTestCase: provides an address() method that - returns the correct address for the doctest case. Otherwise - acts as a proxy to the test case. To provide hints for address(), - an obj may also be passed -- this will be used as the test object - for purposes of determining the test address, if it is provided. - """ - - # Note: this method was taken from numpy's nosetester module. - - # Subclass nose.plugins.doctests.DocTestCase to work around a bug in - # its constructor that blocks non-default arguments from being passed - # down into doctest.DocTestCase - - def __init__(self, test, optionflags=0, setUp=None, tearDown=None, - checker=None, obj=None, result_var='_'): - self._result_var = result_var - doctests.DocTestCase.__init__(self, test, - optionflags=optionflags, - setUp=setUp, tearDown=tearDown, - checker=checker) - # Now we must actually copy the original constructor from the stdlib - # doctest class, because we can't call it directly and a bug in nose - # means it never gets passed the right arguments. - - self._dt_optionflags = optionflags - self._dt_checker = checker - self._dt_test = test - self._dt_test_globs_ori = test.globs - self._dt_setUp = setUp - self._dt_tearDown = tearDown - - # XXX - store this runner once in the object! - runner = IPDocTestRunner(optionflags=optionflags, - checker=checker, verbose=False) - self._dt_runner = runner - - - # Each doctest should remember the directory it was loaded from, so - # things like %run work without too many contortions - self._ori_dir = os.path.dirname(test.filename) - - # Modified runTest from the default stdlib - def runTest(self): - test = self._dt_test - runner = self._dt_runner - - old = sys.stdout - new = StringIO() - optionflags = self._dt_optionflags - - if not (optionflags & REPORTING_FLAGS): - # The option flags don't include any reporting flags, - # so add the default reporting flags - optionflags |= _unittest_reportflags - - try: - # Save our current directory and switch out to the one where the - # test was originally created, in case another doctest did a - # directory change. We'll restore this in the finally clause. - curdir = os.getcwd() - #print 'runTest in dir:', self._ori_dir # dbg - os.chdir(self._ori_dir) - - runner.DIVIDER = "-"*70 - failures, tries = runner.run(test,out=new.write, - clear_globs=False) - finally: - sys.stdout = old - os.chdir(curdir) - - if failures: - raise self.failureException(self.format_failure(new.getvalue())) - - def setUp(self): - """Modified test setup that syncs with ipython namespace""" - #print "setUp test", self._dt_test.examples # dbg - if isinstance(self._dt_test.examples[0], IPExample): - # for IPython examples *only*, we swap the globals with the ipython - # namespace, after updating it with the globals (which doctest - # fills with the necessary info from the module being tested). - self.user_ns_orig = {} - self.user_ns_orig.update(_ip.user_ns) - _ip.user_ns.update(self._dt_test.globs) - # We must remove the _ key in the namespace, so that Python's - # doctest code sets it naturally - _ip.user_ns.pop('_', None) - _ip.user_ns['__builtins__'] = builtin_mod - self._dt_test.globs = _ip.user_ns - - super(DocTestCase, self).setUp() - - def tearDown(self): - - # Undo the test.globs reassignment we made, so that the parent class - # teardown doesn't destroy the ipython namespace - if isinstance(self._dt_test.examples[0], IPExample): - self._dt_test.globs = self._dt_test_globs_ori - _ip.user_ns.clear() - _ip.user_ns.update(self.user_ns_orig) - - # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but - # it does look like one to me: its tearDown method tries to run - # - # delattr(builtin_mod, self._result_var) - # - # without checking that the attribute really is there; it implicitly - # assumes it should have been set via displayhook. But if the - # displayhook was never called, this doesn't necessarily happen. I - # haven't been able to find a little self-contained example outside of - # ipython that would show the problem so I can report it to the nose - # team, but it does happen a lot in our code. - # - # So here, we just protect as narrowly as possible by trapping an - # attribute error whose message would be the name of self._result_var, - # and letting any other error propagate. - try: - super(DocTestCase, self).tearDown() - except AttributeError as exc: - if exc.args[0] != self._result_var: - raise - - # A simple subclassing of the original with a different class name, so we can # distinguish and treat differently IPython examples from pure python ones. class IPExample(doctest.Example): pass @@ -594,169 +450,3 @@ class DocFileCase(doctest.DocFileCase): """ def address(self): return (self._dt_test.filename, None, None) - - -class ExtensionDoctest(doctests.Doctest): - """Nose Plugin that supports doctests in extension modules. - """ - name = 'extdoctest' # call nosetests with --with-extdoctest - enabled = True - - def options(self, parser, env=os.environ): - Plugin.options(self, parser, env) - parser.add_option('--doctest-tests', action='store_true', - dest='doctest_tests', - default=env.get('NOSE_DOCTEST_TESTS',True), - help="Also look for doctests in test modules. " - "Note that classes, methods and functions should " - "have either doctests or non-doctest tests, " - "not both. [NOSE_DOCTEST_TESTS]") - parser.add_option('--doctest-extension', action="append", - dest="doctestExtension", - help="Also look for doctests in files with " - "this extension [NOSE_DOCTEST_EXTENSION]") - # Set the default as a list, if given in env; otherwise - # an additional value set on the command line will cause - # an error. - env_setting = env.get('NOSE_DOCTEST_EXTENSION') - if env_setting is not None: - parser.set_defaults(doctestExtension=tolist(env_setting)) - - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Pull standard doctest plugin out of config; we will do doctesting - config.plugins.plugins = [p for p in config.plugins.plugins - if p.name != 'doctest'] - self.doctest_tests = options.doctest_tests - self.extension = tolist(options.doctestExtension) - - self.parser = doctest.DocTestParser() - self.finder = DocTestFinder() - self.checker = IPDoctestOutputChecker() - self.globs = None - self.extraglobs = None - - - def loadTestsFromExtensionModule(self,filename): - bpath,mod = os.path.split(filename) - modname = os.path.splitext(mod)[0] - try: - sys.path.append(bpath) - module = import_module(modname) - tests = list(self.loadTestsFromModule(module)) - finally: - sys.path.pop() - return tests - - # NOTE: the method below is almost a copy of the original one in nose, with - # a few modifications to control output checking. - - def loadTestsFromModule(self, module): - #print '*** ipdoctest - lTM',module # dbg - - if not self.matches(module.__name__): - log.debug("Doctest doesn't want module %s", module) - return - - tests = self.finder.find(module,globs=self.globs, - extraglobs=self.extraglobs) - if not tests: - return - - # always use whitespace and ellipsis options - optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS - - tests.sort() - module_file = module.__file__ - if module_file[-4:] in ('.pyc', '.pyo'): - module_file = module_file[:-1] - for test in tests: - if not test.examples: - continue - if not test.filename: - test.filename = module_file - - yield DocTestCase(test, - optionflags=optionflags, - checker=self.checker) - - - def loadTestsFromFile(self, filename): - #print "ipdoctest - from file", filename # dbg - if is_extension_module(filename): - for t in self.loadTestsFromExtensionModule(filename): - yield t - else: - if self.extension and anyp(filename.endswith, self.extension): - name = PurePath(filename).name - doc = Path(filename).read_text() - test = self.parser.get_doctest( - doc, globs={'__file__': filename}, name=name, - filename=filename, lineno=0) - if test.examples: - #print 'FileCase:',test.examples # dbg - yield DocFileCase(test) - else: - yield False # no tests to load - - -class IPythonDoctest(ExtensionDoctest): - """Nose Plugin that supports doctests in extension modules. - """ - name = 'ipdoctest' # call nosetests with --with-ipdoctest - enabled = True - - def makeTest(self, obj, parent): - """Look for doctests in the given object, which will be a - function, method or class. - """ - #print 'Plugin analyzing:', obj, parent # dbg - # always use whitespace and ellipsis options - optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS - - doctests = self.finder.find(obj, module=getmodule(parent)) - if doctests: - for test in doctests: - if len(test.examples) == 0: - continue - - yield DocTestCase(test, obj=obj, - optionflags=optionflags, - checker=self.checker) - - def options(self, parser, env=os.environ): - #print "Options for nose plugin:", self.name # dbg - Plugin.options(self, parser, env) - parser.add_option('--ipdoctest-tests', action='store_true', - dest='ipdoctest_tests', - default=env.get('NOSE_IPDOCTEST_TESTS',True), - help="Also look for doctests in test modules. " - "Note that classes, methods and functions should " - "have either doctests or non-doctest tests, " - "not both. [NOSE_IPDOCTEST_TESTS]") - parser.add_option('--ipdoctest-extension', action="append", - dest="ipdoctest_extension", - help="Also look for doctests in files with " - "this extension [NOSE_IPDOCTEST_EXTENSION]") - # Set the default as a list, if given in env; otherwise - # an additional value set on the command line will cause - # an error. - env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') - if env_setting is not None: - parser.set_defaults(ipdoctest_extension=tolist(env_setting)) - - def configure(self, options, config): - #print "Configuring nose plugin:", self.name # dbg - Plugin.configure(self, options, config) - # Pull standard doctest plugin out of config; we will do doctesting - config.plugins.plugins = [p for p in config.plugins.plugins - if p.name != 'doctest'] - self.doctest_tests = options.ipdoctest_tests - self.extension = tolist(options.ipdoctest_extension) - - self.parser = IPDocTestParser() - self.finder = DocTestFinder(parser=self.parser) - self.checker = IPDoctestOutputChecker() - self.globs = None - self.extraglobs = None diff --git a/IPython/testing/plugin/iptest.py b/IPython/testing/plugin/iptest.py deleted file mode 100755 index e24e22a8309..00000000000 --- a/IPython/testing/plugin/iptest.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -"""Nose-based test runner. -""" - -from nose.core import main -from nose.plugins.builtin import plugins -from nose.plugins.doctests import Doctest - -from . import ipdoctest -from .ipdoctest import IPDocTestRunner - -if __name__ == '__main__': - print('WARNING: this code is incomplete!') - print() - - pp = [x() for x in plugins] # activate all builtin plugins first - main(testRunner=IPDocTestRunner(), - plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()]) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index bca72f4666e..db5f1c879ba 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -14,7 +14,6 @@ from os.path import join, abspath from imp import reload -from nose import SkipTest, with_setup import pytest import IPython @@ -98,8 +97,17 @@ def teardown_environment(): if hasattr(sys, 'frozen'): del sys.frozen + # Build decorator that uses the setup_environment/setup_environment -with_environment = with_setup(setup_environment, teardown_environment) +@pytest.fixture +def environment(): + setup_environment() + yield + teardown_environment() + + +with_environment = pytest.mark.usefixtures("environment") + @skip_if_not_win32 @with_environment @@ -291,7 +299,8 @@ def test_not_writable_ipdir(self): else: # I can still write to an unwritable dir, # assume I'm root and skip the test - raise SkipTest("I can't create directories that I can't write to") + pytest.skip("I can't create directories that I can't write to") + with self.assertWarnsRegex(UserWarning, 'is not a writable location'): ipdir = paths.get_ipython_dir() env.pop('IPYTHON_DIR', None) diff --git a/appveyor.yml b/appveyor.yml index c3702755110..0a7b388401e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,13 +26,9 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - - pip install nose coverage pytest pytest-cov pytest-trio matplotlib pandas + - pip install pytest pytest-cov pytest-trio matplotlib pandas - pip install -e .[test] - - mkdir results - - cd results test_script: - - iptest --coverage xml - - cd .. - pytest --color=yes -ra --cov --cov-report=xml on_finish: - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe diff --git a/docs/environment.yml b/docs/environment.yml index d24ae3129dc..b35ceb7a291 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -5,7 +5,6 @@ dependencies: - sphinx>=1.8 - sphinx_rtd_theme - numpy -- nose - testpath - matplotlib - pip: diff --git a/docs/source/install/install.rst b/docs/source/install/install.rst index 5804fa83d52..a4168c4f14a 100644 --- a/docs/source/install/install.rst +++ b/docs/source/install/install.rst @@ -100,12 +100,11 @@ permissions, you may need to run the last command with :command:`sudo`. You can also install in user specific location by using the ``--user`` flag in conjunction with pip. -To run IPython's test suite, use the :command:`iptest` command from outside of -the IPython source tree: +To run IPython's test suite, use the :command:`pytest` command: .. code-block:: bash - $ iptest + $ pytest .. _devinstall: diff --git a/setup.py b/setup.py index 6c30c9f4043..c05a769a0ea 100755 --- a/setup.py +++ b/setup.py @@ -175,7 +175,6 @@ qtconsole=["qtconsole"], doc=["Sphinx>=1.3"], test=[ - "nose>=0.10.1", "pytest", "requests", "testpath", diff --git a/setupbase.py b/setupbase.py index ac2acc069f4..97cebe807e1 100644 --- a/setupbase.py +++ b/setupbase.py @@ -234,7 +234,6 @@ def find_entry_points(): """ ep = [ 'ipython%s = IPython:start_ipython', - 'iptest%s = IPython.testing.iptestcontroller:main', ] suffix = str(sys.version_info[0]) return [e % '' for e in ep] + [e % suffix for e in ep] diff --git a/tools/release_helper.sh b/tools/release_helper.sh index 1c1d5e6cdb1..4d5672b498b 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -7,7 +7,7 @@ python -c 'import keyring' python -c 'import twine' python -c 'import sphinx' python -c 'import sphinx_rtd_theme' -python -c 'import nose' +python -c 'import pytest' BLACK=$(tput setaf 1) From 28bb4c88e371132e2c825a4f4776a19a1dde8f6f Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 8 Nov 2021 20:55:35 +0300 Subject: [PATCH 1686/3726] find_cmd is not using pywin32 for a while already --- IPython/utils/tests/test_process.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index f4ee3859ed8..3ac479f048d 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -44,23 +44,13 @@ def test_find_cmd_ls(): assert path.endswith("ls") -def has_pywin32(): - try: - import win32api - except ImportError: - return False - return True - - -@dec.onlyif(has_pywin32, "This test requires win32api to run") +@dec.skip_if_not_win32 def test_find_cmd_pythonw(): """Try to find pythonw on Windows.""" path = find_cmd('pythonw') assert path.lower().endswith('pythonw.exe'), path -@dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(), - "This test runs on posix or in win32 with win32api installed") def test_find_cmd_fail(): """Make sure that FindCmdError is raised if we can't find the cmd.""" pytest.raises(FindCmdError, find_cmd, "asdfasdf") From 7f364bb801ae9511908f6681120c4e89e2607ebf Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 14 Nov 2021 01:54:48 +0300 Subject: [PATCH 1687/3726] Port `deepreload` to `importlib` `imp` is deprecated since Python 3.3 and scheduled for removal in Python 3.12 The code is severely undertested, I added several, and also fixed a bug that `modules_reloading` was not cleaned-up on an import failure. --- IPython/lib/deepreload.py | 58 ++++------------------------ IPython/lib/tests/test_deepreload.py | 28 ++++++++++++-- 2 files changed, 33 insertions(+), 53 deletions(-) diff --git a/IPython/lib/deepreload.py b/IPython/lib/deepreload.py index ddfa1bc603d..fa8009b59c1 100644 --- a/IPython/lib/deepreload.py +++ b/IPython/lib/deepreload.py @@ -28,7 +28,7 @@ import builtins as builtin_mod from contextlib import contextmanager -import imp +import importlib import sys from types import ModuleType @@ -174,33 +174,17 @@ def import_submodule(mod, subname, fullname): print('Reloading', fullname) found_now[fullname] = 1 oldm = sys.modules.get(fullname, None) - - if mod is None: - path = None - elif hasattr(mod, '__path__'): - path = mod.__path__ - else: - return None - - try: - # This appears to be necessary on Python 3, because imp.find_module() - # tries to import standard libraries (like io) itself, and we don't - # want them to be processed by our deep_import_hook. - with replace_import_hook(original_import): - fp, filename, stuff = imp.find_module(subname, path) - except ImportError: - return None - try: - m = imp.load_module(fullname, fp, filename, stuff) + if oldm is not None: + m = importlib.reload(oldm) + else: + m = importlib.import_module(subname, mod) except: # load_module probably removed name from modules because of # the error. Put back the original module object. if oldm: sys.modules[fullname] = oldm raise - finally: - if fp: fp.close() add_submodule(mod, m, fullname, subname) @@ -285,43 +269,17 @@ def deep_reload_hook(m): except: modules_reloading[name] = m - dot = name.rfind('.') - if dot < 0: - subname = name - path = None - else: - try: - parent = sys.modules[name[:dot]] - except KeyError as e: - modules_reloading.clear() - raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot]) from e - subname = name[dot+1:] - path = getattr(parent, "__path__", None) - - try: - # This appears to be necessary on Python 3, because imp.find_module() - # tries to import standard libraries (like io) itself, and we don't - # want them to be processed by our deep_import_hook. - with replace_import_hook(original_import): - fp, filename, stuff = imp.find_module(subname, path) - finally: - modules_reloading.clear() - try: - newm = imp.load_module(name, fp, filename, stuff) + newm = importlib.reload(m) except: - # load_module probably removed name from modules because of - # the error. Put back the original module object. sys.modules[name] = m raise finally: - if fp: fp.close() - - modules_reloading.clear() + modules_reloading.clear() return newm # Save the original hooks -original_reload = imp.reload +original_reload = importlib.reload # Replacement for reload() def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__', diff --git a/IPython/lib/tests/test_deepreload.py b/IPython/lib/tests/test_deepreload.py index d9af0f1dbbd..9759a7f69f7 100644 --- a/IPython/lib/tests/test_deepreload.py +++ b/IPython/lib/tests/test_deepreload.py @@ -4,11 +4,14 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import pytest +import types + from pathlib import Path from IPython.utils.syspathcontext import prepended_to_syspath from IPython.utils.tempdir import TemporaryDirectory -from IPython.lib.deepreload import reload as dreload +from IPython.lib.deepreload import reload as dreload, modules_reloading def test_deepreload(): @@ -17,9 +20,9 @@ def test_deepreload(): with prepended_to_syspath(tmpdir): tmpdirpath = Path(tmpdir) with open(tmpdirpath / "A.py", "w") as f: - f.write("class Object(object):\n pass\n") + f.write("class Object:\n pass\nok = True\n") with open(tmpdirpath / "B.py", "w") as f: - f.write("import A\n") + f.write("import A\nassert A.ok, 'we are fine'\n") import A import B @@ -28,7 +31,26 @@ def test_deepreload(): dreload(B, exclude=["A"]) assert isinstance(obj, A.Object) is True + # Test that an import failure will not blow-up us. + A.ok = False + with pytest.raises(AssertionError, match="we are fine"): + dreload(B, exclude=["A"]) + assert len(modules_reloading) == 0 + assert not A.ok + # Test that A is reloaded. obj = A.Object() + A.ok = False dreload(B) + assert A.ok assert isinstance(obj, A.Object) is False + + +def test_not_module(): + pytest.raises(TypeError, dreload, "modulename") + + +def test_not_in_sys_modules(): + fake_module = types.ModuleType("fake_module") + with pytest.raises(ImportError, match="not in sys.modules"): + dreload(fake_module) From 75fd024c98b4552bf3ce8692f2442aff5c012fc6 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 13 Nov 2021 04:31:23 +0300 Subject: [PATCH 1688/3726] Fix `history_manager.search(?, unique=True)` returning old entries --- IPython/core/history.py | 12 +++++++++--- IPython/core/tests/test_history.py | 13 +++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/IPython/core/history.py b/IPython/core/history.py index ff931d20dd7..43248066091 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -265,7 +265,7 @@ def writeout_cache(self): ## ------------------------------- ## Methods for retrieving history: ## ------------------------------- - def _run_sql(self, sql, params, raw=True, output=False): + def _run_sql(self, sql, params, raw=True, output=False, latest=False): """Prepares and runs an SQL query for the history database. Parameters @@ -276,6 +276,8 @@ def _run_sql(self, sql, params, raw=True, output=False): Parameters passed to the SQL query (to replace "?") raw, output : bool See :meth:`get_range` + latest : bool + Select rows with max (session, line) Returns ------- @@ -286,8 +288,12 @@ def _run_sql(self, sql, params, raw=True, output=False): if output: sqlfrom = "history LEFT JOIN output_history USING (session, line)" toget = "history.%s, output_history.output" % toget + if latest: + toget += ", MAX(session * 128 * 1024 + line)" cur = self.db.execute("SELECT session, line, %s FROM %s " %\ (toget, sqlfrom) + sql, params) + if latest: + cur = (row[:-1] for row in cur) if output: # Regroup into 3-tuples, and parse JSON return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur @@ -395,7 +401,7 @@ def search(self, pattern="*", raw=True, search_raw=True, params += (n,) elif unique: sqlform += " ORDER BY session, line" - cur = self._run_sql(sqlform, params, raw=raw, output=output) + cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique) if n is not None: return reversed(list(cur)) return cur @@ -817,7 +823,7 @@ def run(self): try: self.db = sqlite3.connect( str(self.history_manager.hist_file), - **self.history_manager.connection_options + **self.history_manager.connection_options, ) while True: self.history_manager.save_flag.wait() diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index 4cc43caeb6b..6d6a1b1dd15 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -17,12 +17,10 @@ from traitlets.config.loader import Config from IPython.utils.tempdir import TemporaryDirectory from IPython.core.history import HistoryManager, extract_hist_ranges -from IPython.testing.decorators import skipif def test_proper_default_encoding(): assert sys.getdefaultencoding() == "utf-8" -@skipif(sqlite3.sqlite_version_info > (3,24,0)) def test_history(): ip = get_ipython() with TemporaryDirectory() as tmpdir: @@ -133,6 +131,17 @@ def test_history(): ip.history_manager.store_inputs(1, "rogue") ip.history_manager.writeout_cache() assert ip.history_manager.session_number == 3 + + # Check that session and line values are not just max values + sessid, lineno, entry = newhist[-1] + assert lineno > 1 + ip.history_manager.reset() + lineno = 1 + ip.history_manager.store_inputs(lineno, entry) + gothist = ip.history_manager.search("*=*", unique=True) + hist = list(gothist)[-1] + assert sessid < hist[0] + assert hist[1:] == (lineno, entry) finally: # Ensure saving thread is shut down before we try to clean up the files ip.history_manager.save_thread.stop() From fc34990c713b036e4ae24d037fa37f844b19fb5b Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 14 Nov 2021 22:45:09 +0300 Subject: [PATCH 1689/3726] Deprecate `utils.sysinfo.num_cpus` `os.cpu_count()` exists since Python 3.4 --- IPython/utils/sysinfo.py | 54 ++++++++--------------------- IPython/utils/tests/test_sysinfo.py | 6 ++++ 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/IPython/utils/sysinfo.py b/IPython/utils/sysinfo.py index fed82a24537..857f0cf2d84 100644 --- a/IPython/utils/sysinfo.py +++ b/IPython/utils/sysinfo.py @@ -118,49 +118,25 @@ def sys_info(): """ return pprint.pformat(get_sys_info()) -def _num_cpus_unix(): - """Return the number of active CPUs on a Unix system.""" - return os.sysconf("SC_NPROCESSORS_ONLN") - - -def _num_cpus_darwin(): - """Return the number of active CPUs on a Darwin system.""" - p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE) - return p.stdout.read() - - -def _num_cpus_windows(): - """Return the number of active CPUs on a Windows system.""" - return os.environ.get("NUMBER_OF_PROCESSORS") - def num_cpus(): - """Return the effective number of CPUs in the system as an integer. - - This cross-platform function makes an attempt at finding the total number of - available CPUs in the system, as returned by various underlying system and - python calls. + """DEPRECATED - If it can't find a sensible answer, it returns 1 (though an error *may* make - it return a large positive number that's actually incorrect). - """ + Return the effective number of CPUs in the system as an integer. - # Many thanks to the Parallel Python project (http://www.parallelpython.com) - # for the names of the keys we needed to look up for this function. This - # code was inspired by their equivalent function. + This cross-platform function makes an attempt at finding the total number of + available CPUs in the system, as returned by various underlying system and + python calls. - ncpufuncs = {'Linux':_num_cpus_unix, - 'Darwin':_num_cpus_darwin, - 'Windows':_num_cpus_windows - } - - ncpufunc = ncpufuncs.get(platform.system(), - # default to unix version (Solaris, AIX, etc) - _num_cpus_unix) + If it can't find a sensible answer, it returns 1 (though an error *may* make + it return a large positive number that's actually incorrect). + """ + import warnings - try: - ncpus = max(1,int(ncpufunc())) - except: - ncpus = 1 - return ncpus + warnings.warn( + "`num_cpus` is deprecated since IPython 8.0. Use `os.cpu_count` instead.", + DeprecationWarning, + stacklevel=2, + ) + return os.cpu_count() or 1 diff --git a/IPython/utils/tests/test_sysinfo.py b/IPython/utils/tests/test_sysinfo.py index 23f80e4e5ff..07dd22d02e3 100644 --- a/IPython/utils/tests/test_sysinfo.py +++ b/IPython/utils/tests/test_sysinfo.py @@ -5,6 +5,7 @@ # Distributed under the terms of the Modified BSD License. import json +import pytest from IPython.utils import sysinfo @@ -14,3 +15,8 @@ def test_json_getsysinfo(): test that it is easily jsonable and don't return bytes somewhere. """ json.dumps(sysinfo.get_sys_info()) + + +def test_num_cpus(): + with pytest.deprecated_call(): + sysinfo.num_cpus() From 5afec8019772d73484eea9d768dfa8c526739fbf Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 15 Nov 2021 08:26:43 -0800 Subject: [PATCH 1690/3726] completer documentation --- IPython/core/completer.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 736d226d26f..57bfa9d9722 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -2057,6 +2057,24 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, ``column`` when passing multiline strings this could/should be renamed but would add extra noise. + Parameters + ---------- + cursor_line : + Index of the line the cursor is on. 0 indexed. + cursor_pos : + Position of the cursor in the current line/line_buffer/text. 0 + indexed. + line_buffer : optional, str + The current line the cursor is in, this is mostly due to legacy + reason that readline coudl only give a us the single current line. + Prefer `full_text`. + text : str + The current "token" the cursor is in, mostly also for historical + reasons. as the completer would trigger only after the current line + was parsed. + full_text : str + Full text of the current cell. + Returns ------- A tuple of N elements which are (likely): From 30e10db258e66e61b70d6d91c7e478b88db386fc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 15 Nov 2021 10:04:53 -0800 Subject: [PATCH 1691/3726] manual reformat --- IPython/core/tests/test_iplib.py | 74 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index 5cbc77251db..9bdfd514644 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -40,48 +40,48 @@ def test_reset(): def doctest_tb_plain(): """ -In [18]: xmode plain -Exception reporting mode: Plain - - In [19]: run simpleerr.py - Traceback (most recent call last): - ...line ..., in - bar(mode) - ...line ..., in bar - div0() - ...line ..., in div0 - x/y - ZeroDivisionError: ... + In [18]: xmode plain + Exception reporting mode: Plain + + In [19]: run simpleerr.py + Traceback (most recent call last): + ...line ..., in + bar(mode) + ...line ..., in bar + div0() + ...line ..., in div0 + x/y + ZeroDivisionError: ... """ def doctest_tb_context(): """ -In [3]: xmode context -Exception reporting mode: Context - - In [4]: run simpleerr.py - --------------------------------------------------------------------------- - ZeroDivisionError Traceback (most recent call last) - - ... in - 30 except IndexError: - 31 mode = 'div' - ---> 33 bar(mode) - - ... in bar(mode) - 15 "bar" - 16 if mode=='div': - ---> 17 div0() - 18 elif mode=='exit': - 19 try: - - ... in div0() - 6 x = 1 - 7 y = 0 - ----> 8 x/y - - ZeroDivisionError: ...""" + In [3]: xmode context + Exception reporting mode: Context + + In [4]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... in + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ... in bar(mode) + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + + ZeroDivisionError: ...""" def doctest_tb_verbose(): From 04eb390c57db717dfa7de5963f7e75923b29f165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Luis=20Cano=20Rodr=C3=ADguez?= Date: Tue, 9 Nov 2021 10:20:30 +0100 Subject: [PATCH 1692/3726] Update documentation dependencies --- docs/environment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/environment.yml b/docs/environment.yml index b35ceb7a291..e75373e17c9 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -2,8 +2,8 @@ name: ipython_docs dependencies: - python=3.8 - setuptools>=18.5 -- sphinx>=1.8 -- sphinx_rtd_theme +- sphinx>=4.2.0 +- sphinx_rtd_theme>=1.0.0 - numpy - testpath - matplotlib From 1a20e9318e792bc8a24789d79dae6dad7795f55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Luis=20Cano=20Rodr=C3=ADguez?= Date: Fri, 12 Nov 2021 21:01:16 +0100 Subject: [PATCH 1693/3726] Update environment.yml --- docs/environment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/environment.yml b/docs/environment.yml index e75373e17c9..628f31ee7b8 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -2,8 +2,8 @@ name: ipython_docs dependencies: - python=3.8 - setuptools>=18.5 -- sphinx>=4.2.0 -- sphinx_rtd_theme>=1.0.0 +- sphinx>=4.2 +- sphinx_rtd_theme>=1.0 - numpy - testpath - matplotlib From f1a6a47954e56fb11999f6ddcd7e996f498b01c8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 12 Nov 2021 16:00:47 -0800 Subject: [PATCH 1694/3726] try to fix enyml --- docs/environment.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/environment.yml b/docs/environment.yml index 628f31ee7b8..afb5eff0051 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -1,13 +1,17 @@ name: ipython_docs +channels: + - conda-forge + - defaults dependencies: -- python=3.8 -- setuptools>=18.5 -- sphinx>=4.2 -- sphinx_rtd_theme>=1.0 -- numpy -- testpath -- matplotlib -- pip: + - python=3.8 + - setuptools>=18.5 + - sphinx>=4.2 + - sphinx_rtd_theme>=1.0 + - numpy + - nose + - testpath + - matplotlib + - pip: - docrepr - prompt_toolkit - ipykernel From 95fa8aeb6b109a9d9694659ae198a254896b1c5c Mon Sep 17 00:00:00 2001 From: Gal B Date: Sat, 13 Nov 2021 17:45:33 +0000 Subject: [PATCH 1695/3726] Remove always skipped test. See: #13219 --- IPython/core/tests/test_completer.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 61e1d35c1db..4d60bafbf8b 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -316,18 +316,6 @@ def test_forward_unicode_completion(self): self.assertEqual(matches, ["Ⅴ"]) # This is not a V self.assertEqual(matches, ["\u2164"]) # same as above but explicit. - @unittest.skip("now we have a completion for \jmath") - @pytest.mark.xfail( - sys.platform == "win32", reason="Fails if there is a C:\\j... path" - ) - def test_no_ascii_back_completion(self): - ip = get_ipython() - with TemporaryWorkingDirectory(): # Avoid any filename completions - # single ascii letter that don't have yet completions - for letter in "jJ": - name, matches = ip.complete("\\" + letter) - self.assertEqual(matches, []) - def test_delim_setting(self): sp = completer.CompletionSplitter() sp.delims = " " From fc57ec912eec9da4c33d8db5416fdb2dada706e7 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 19:09:58 +0300 Subject: [PATCH 1696/3726] Handle changed exception type from inspect.getabsfile (bpo-44648) https://bugs.python.org/issue44648 (Python 3.10+) --- IPython/core/oinspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index ab25eeeffca..09b74700ba0 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -305,13 +305,13 @@ def find_file(obj) -> str: fname = None try: fname = inspect.getabsfile(obj) - except TypeError: + except (OSError, TypeError): # For an instance, the file that matters is where its class was # declared. if hasattr(obj, '__class__'): try: fname = inspect.getabsfile(obj.__class__) - except TypeError: + except (OSError, TypeError): # Can happen for builtins pass except: From 8134c2ebd582daa3e2caa664ad310c70594de7f5 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 19:09:58 +0300 Subject: [PATCH 1697/3726] Workaround argparse changed text in Python 3.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “optional arguments” was replaced with “options” in argparse help. https://docs.python.org/3/whatsnew/3.10.html#argparse https://bugs.python.org/issue9694 --- IPython/core/tests/test_magic_arguments.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/IPython/core/tests/test_magic_arguments.py b/IPython/core/tests/test_magic_arguments.py index 62946dc5343..8b263b25d6f 100644 --- a/IPython/core/tests/test_magic_arguments.py +++ b/IPython/core/tests/test_magic_arguments.py @@ -7,7 +7,7 @@ #----------------------------------------------------------------------------- import argparse -import pytest +import sys from IPython.core.magic_arguments import (argument, argument_group, kwds, magic_arguments, parse_argstring, real_name) @@ -74,9 +74,14 @@ def foo(self, args): def test_magic_arguments(): + # “optional arguments” was replaced with “options” in argparse help + # https://docs.python.org/3/whatsnew/3.10.html#argparse + # https://bugs.python.org/issue9694 + options = "optional arguments" if sys.version_info < (3, 10) else "options" + assert ( magic_foo1.__doc__ - == "::\n\n %foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo1, "argcmd_name", None) == None assert real_name(magic_foo1) == "foo1" @@ -91,7 +96,7 @@ def test_magic_arguments(): assert ( magic_foo3.__doc__ - == "::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" + == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" ) assert getattr(magic_foo3, "argcmd_name", None) == None assert real_name(magic_foo3) == "foo3" @@ -100,7 +105,7 @@ def test_magic_arguments(): assert ( magic_foo4.__doc__ - == "::\n\n %foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo4, "argcmd_name", None) == None assert real_name(magic_foo4) == "foo4" @@ -109,7 +114,7 @@ def test_magic_arguments(): assert ( magic_foo5.__doc__ - == "::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate" assert real_name(magic_foo5) == "frobnicate" @@ -118,7 +123,7 @@ def test_magic_arguments(): assert ( magic_magic_foo.__doc__ - == "::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_magic_foo, "argcmd_name", None) == None assert real_name(magic_magic_foo) == "magic_foo" @@ -127,7 +132,7 @@ def test_magic_arguments(): assert ( foo.__doc__ - == "::\n\n %foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" ) assert getattr(foo, "argcmd_name", None) == None assert real_name(foo) == "foo" From e63a8bbaa6a458d749ed275140ae55ecbc906154 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 19:09:58 +0300 Subject: [PATCH 1698/3726] Workaround Jedi Python 3.10 issue --- IPython/core/tests/test_completer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 61e1d35c1db..7ef4892e929 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -26,6 +26,15 @@ _deduplicate_completions, ) +if sys.version_info >= (3, 10): + import jedi + from pkg_resources import parse_version + + # Requires https://github.com/davidhalter/jedi/pull/1795 + jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0") +else: + jedi_issue = False + # ----------------------------------------------------------------------------- # Test functions # ----------------------------------------------------------------------------- @@ -442,6 +451,8 @@ def test_all_completions_dups(self): matches = c.all_completions("TestCl") assert matches == ['TestClass'], jedi_status matches = c.all_completions("TestClass.") + if jedi_status and jedi_issue: + continue assert len(matches) > 2, jedi_status matches = c.all_completions("TestClass.a") assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status @@ -496,6 +507,7 @@ def test_completion_have_signature(self): "encoding" in c.signature ), "Signature of function was not found by completer" + @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0") def test_deduplicate_completions(self): """ Test that completions are correctly deduplicated (even if ranges are not the same) From d0d8c07bad43bd558a8e7f4bdce9abdc3be89768 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Thu, 7 Jan 2021 14:43:46 +0100 Subject: [PATCH 1699/3726] xxlimited module is xxlimited_35 in Python 3.10 --- IPython/lib/tests/test_pretty.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 8c116300b34..ec1211ac195 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -7,14 +7,16 @@ from collections import Counter, defaultdict, deque, OrderedDict import os +import pytest import types import string +import sys import unittest import pytest from IPython.lib import pretty -from IPython.testing.decorators import skip_without, skip_iptest_but_not_pytest +from IPython.testing.decorators import skip_iptest_but_not_pytest from io import StringIO @@ -136,12 +138,12 @@ def test_sets(obj, expected_output): assert got_output == expected_output -@skip_without('xxlimited') def test_pprint_heap_allocated_type(): """ Test that pprint works for heap allocated types. """ - import xxlimited + module_name = "xxlimited" if sys.version_info < (3, 10) else "xxlimited_35" + xxlimited = pytest.importorskip(module_name) output = pretty.pretty(xxlimited.Null) assert output == "xxlimited.Null" From 39ea7f36a044a7ac6bbb6a08244215de0ba3b20c Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 15 Nov 2021 21:10:16 +0300 Subject: [PATCH 1700/3726] Add Python 3.10 CI runners --- .github/workflows/test.yml | 4 ++-- appveyor.yml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c79db6e0f7f..9bd353a97e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,13 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10"] # Test all on ubuntu, test ends on macos include: - os: macos-latest python-version: "3.7" - os: macos-latest - python-version: "3.9" + python-version: "3.10" steps: - uses: actions/checkout@v2 diff --git a/appveyor.yml b/appveyor.yml index 0a7b388401e..10659d8e8b1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,10 +4,15 @@ matrix: environment: global: + APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' COLUMNS: 120 # Appveyor web viwer window width is 130 chars matrix: + - PYTHON: "C:\\Python310-x64" + PYTHON_VERSION: "3.10.x" + PYTHON_ARCH: "64" + - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.x" PYTHON_ARCH: "64" From d23958a156c5460009d6a79cdc92d7392d0814ea Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 15 Nov 2021 11:26:45 -0800 Subject: [PATCH 1701/3726] Move dict keys completions higher. If we are in a dict this is likely way more accurate. --- IPython/core/completer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 57bfa9d9722..c6d20bbf5af 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -1191,18 +1191,18 @@ def matchers(self) -> List[Any]: if self.use_jedi: return [ *self.custom_matchers, + self.dict_key_matches, self.file_matches, self.magic_matches, - self.dict_key_matches, ] else: return [ *self.custom_matchers, + self.dict_key_matches, self.python_matches, self.file_matches, self.magic_matches, self.python_func_kw_matches, - self.dict_key_matches, ] def all_completions(self, text:str) -> List[str]: From 856b05957523262c7fc7262a09d5c9cb8b78c225 Mon Sep 17 00:00:00 2001 From: Gal B Date: Sat, 13 Nov 2021 19:30:58 +0000 Subject: [PATCH 1702/3726] Use pathlib in historyapp.py --- IPython/core/historyapp.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/IPython/core/historyapp.py b/IPython/core/historyapp.py index a6437eff26e..01a55343f8a 100644 --- a/IPython/core/historyapp.py +++ b/IPython/core/historyapp.py @@ -5,8 +5,8 @@ To be invoked as the `ipython history` subcommand. """ -import os import sqlite3 +from pathlib import Path from traitlets.config.application import Application from .application import BaseIPythonApplication @@ -52,8 +52,8 @@ class HistoryTrim(BaseIPythonApplication): )) def start(self): - profile_dir = self.profile_dir.location - hist_file = os.path.join(profile_dir, 'history.sqlite') + profile_dir = Path(self.profile_dir.location) + hist_file = profile_dir / "history.sqlite" con = sqlite3.connect(hist_file) # Grab the recent history from the current database. @@ -77,12 +77,12 @@ def start(self): con.close() # Create the new history database. - new_hist_file = os.path.join(profile_dir, 'history.sqlite.new') + new_hist_file = profile_dir / "history.sqlite.new" i = 0 - while os.path.exists(new_hist_file): + while new_hist_file.exists(): # Make sure we don't interfere with an existing file. i += 1 - new_hist_file = os.path.join(profile_dir, 'history.sqlite.new'+str(i)) + new_hist_file = profile_dir / ("history.sqlite.new" + str(i)) new_db = sqlite3.connect(new_hist_file) new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer primary key autoincrement, start timestamp, @@ -106,16 +106,16 @@ def start(self): if self.backup: i = 1 - backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i) - while os.path.exists(backup_hist_file): + backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i) + while backup_hist_file.exists(): i += 1 - backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i) - os.rename(hist_file, backup_hist_file) + backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i) + hist_file.rename(backup_hist_file) print("Backed up longer history file to", backup_hist_file) else: - os.remove(hist_file) + hist_file.unlink() - os.rename(new_hist_file, hist_file) + new_hist_file.rename(hist_file) class HistoryClear(HistoryTrim): description = clear_hist_help From 6b90f9ce9f0e793f05f586e478508f0c4a4e3d03 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 16 Nov 2021 21:23:05 +0300 Subject: [PATCH 1703/3726] Switch out off deprecated `asyncio.get_event_loop` call --- IPython/core/async_helpers.py | 2 +- IPython/core/magics/script.py | 4 ++-- IPython/core/tests/test_interactiveshell.py | 2 +- IPython/core/tests/test_magic.py | 21 +++++++++++++-------- IPython/terminal/interactiveshell.py | 2 +- IPython/terminal/pt_inputhooks/asyncio.py | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 67ff169c774..9d2e5ddaab0 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -25,7 +25,7 @@ def __call__(self, coro): """ import asyncio - return asyncio.get_event_loop().run_until_complete(coro) + return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro) def __str__(self): return 'asyncio' diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index 5bf6a023e13..b9f3b8fb157 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -81,7 +81,7 @@ def safe_watcher(): yield return - loop = asyncio.get_event_loop() + loop = policy.get_event_loop() try: watcher = asyncio.SafeChildWatcher() watcher.attach_loop(loop) @@ -238,7 +238,7 @@ async def _stream_communicate(process, cell): if sys.platform.startswith("win"): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - loop = asyncio.get_event_loop() + loop = asyncio.get_event_loop_policy().get_event_loop() argv = arg_split(line, posix=not sys.platform.startswith("win")) args, cmd = self.shebang.parser.parse_known_args(argv) try: diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index ef7d69ff8be..f2185e45e76 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -1039,7 +1039,7 @@ def test_custom_exc_count(): def test_run_cell_async(): - loop = asyncio.get_event_loop() + loop = asyncio.get_event_loop_policy().get_event_loop() ip.run_cell("import asyncio") coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5") assert asyncio.iscoroutine(coro) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 9c08926a3f3..cf4c31eb6e9 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -967,17 +967,22 @@ def test_script_config(): assert "whoda" in sm.magics["cell"] +@pytest.fixture +def event_loop(): + yield asyncio.get_event_loop_policy().get_event_loop() + + @dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" ) -def test_script_out(): - assert asyncio.get_event_loop().is_running() is False +def test_script_out(event_loop): + assert event_loop.is_running() is False ip = get_ipython() ip.run_cell_magic("script", "--out output sh", "echo 'hi'") - assert asyncio.get_event_loop().is_running() is False + assert event_loop.is_running() is False assert ip.user_ns["output"] == "hi\n" @@ -986,11 +991,11 @@ def test_script_out(): @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" ) -def test_script_err(): +def test_script_err(event_loop): ip = get_ipython() - assert asyncio.get_event_loop().is_running() is False + assert event_loop.is_running() is False ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") - assert asyncio.get_event_loop().is_running() is False + assert event_loop.is_running() is False assert ip.user_ns["error"] == "hello\n" @@ -1014,12 +1019,12 @@ def test_script_out_err(): @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" ) -async def test_script_bg_out(): +async def test_script_bg_out(event_loop): ip = get_ipython() ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") assert (await ip.user_ns["output"].read()) == b"hi\n" ip.user_ns["output"].close() - asyncio.get_event_loop().stop() + event_loop.stop() @dec.skip_iptest_but_not_pytest diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index fd1e0035237..7207a572408 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -648,7 +648,7 @@ def enable_gui(self, gui=None): # When we integrate the asyncio event loop, run the UI in the # same event loop as the rest of the code. don't use an actual # input hook. (Asyncio is not made for nesting event loops.) - self.pt_loop = asyncio.get_event_loop() + self.pt_loop = asyncio.get_event_loop_policy().get_event_loop() elif self._inputhook: # If an inputhook was set, create a new asyncio event loop with diff --git a/IPython/terminal/pt_inputhooks/asyncio.py b/IPython/terminal/pt_inputhooks/asyncio.py index 95cf194f866..72c45ea251e 100644 --- a/IPython/terminal/pt_inputhooks/asyncio.py +++ b/IPython/terminal/pt_inputhooks/asyncio.py @@ -35,7 +35,7 @@ # Keep reference to the original asyncio loop, because getting the event loop # within the input hook would return the other loop. -loop = asyncio.get_event_loop() +loop = asyncio.get_event_loop_policy().get_event_loop() def inputhook(context): From ad4aa0fcc43326cd3abdc8c705ebb9db2b643b72 Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Tue, 16 Nov 2021 10:57:19 -0800 Subject: [PATCH 1704/3726] use sphinxify (if available) in object_inspect_mime path Background: if we use `%pinfo` line magic (or equivalently `?`; and similarly for `pinfo2` and `??`), we will call `interactiveshell`'s `_inspect` method which automatically uses the docrepr module (if available and activated) to beautifully format docstrings. However, if we use the lower-level `object_inspect_mime` public method in `interactiveshell`, docrepr is not used. (Sidenote: practically speaking, this function is called from JupyterLab's Context Help, which creates an "inspect_request" message, which in turns calls `object_inspect_mime`.) This commit enables `object_inspect_mime` to use the same functionality as `_inspect`. --- IPython/core/interactiveshell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index c831aa4096f..e76e2142bae 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1813,8 +1813,9 @@ def object_inspect_mime(self, oname, detail_level=0): with self.builtin_trap: info = self._object_find(oname) if info.found: + docformat = sphinxify if self.sphinxify_docstring else None return self.inspector._get_info(info.obj, oname, info=info, - detail_level=detail_level + detail_level=detail_level, formatter=docformat ) else: raise KeyError(oname) From aec60658534e2c0a55d36559c19d67dd06a44634 Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Tue, 16 Nov 2021 12:12:46 -0800 Subject: [PATCH 1705/3726] darker --- IPython/core/interactiveshell.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index e76e2142bae..0dec7535356 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1814,8 +1814,12 @@ def object_inspect_mime(self, oname, detail_level=0): info = self._object_find(oname) if info.found: docformat = sphinxify if self.sphinxify_docstring else None - return self.inspector._get_info(info.obj, oname, info=info, - detail_level=detail_level, formatter=docformat + return self.inspector._get_info( + info.obj, + oname, + info=info, + detail_level=detail_level, + formatter=docformat, ) else: raise KeyError(oname) From d0c8b31d178b7880de1c9ccd5f0ab7ff8e164d23 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 02:08:42 +0300 Subject: [PATCH 1706/3726] module_paths: Fix confusing things --- IPython/utils/module_paths.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/IPython/utils/module_paths.py b/IPython/utils/module_paths.py index b5ae60c3322..f9f7cacc332 100644 --- a/IPython/utils/module_paths.py +++ b/IPython/utils/module_paths.py @@ -2,8 +2,6 @@ Utility functions for finding modules on sys.path. -`find_module` returns a path to module or None, given certain conditions. - """ #----------------------------------------------------------------------------- # Copyright (c) 2011, the IPython Development Team. @@ -59,11 +57,11 @@ def find_mod(module_name): Path to module `module_name`, its __init__.py, or None, depending on above conditions. """ - loader = importlib.util.find_spec(module_name) - module_path = loader.origin + spec = importlib.util.find_spec(module_name) + module_path = spec.origin if module_path is None: - if loader.loader in sys.meta_path: - return loader.loader + if spec.loader in sys.meta_path: + return spec.loader return None else: split_path = module_path.split(".") From b89cba42488d45bcc37045bc7fe3cce31c645977 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 02:09:10 +0300 Subject: [PATCH 1707/3726] test_path: Replace imp.reload with importlib.reload --- IPython/utils/tests/test_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index db5f1c879ba..57619a203c1 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -12,7 +12,7 @@ from contextlib import contextmanager from unittest.mock import patch from os.path import join, abspath -from imp import reload +from importlib import reload import pytest From 579c9f2a35c0347a5864e5f4886d49031852f765 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 02:10:35 +0300 Subject: [PATCH 1708/3726] MyTempImporter: implement modern interface --- IPython/core/tests/test_magic.py | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 9c08926a3f3..93542c67d1d 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1332,24 +1332,23 @@ def test_run_module_from_import_hook(): fullpath = os.path.join(tmpdir, 'my_tmp.py') Path(fullpath).write_text(TEST_MODULE) - class MyTempImporter(object): - def __init__(self): - pass - - def find_module(self, fullname, path=None): - if 'my_tmp' in fullname: - return self - return None - - def load_module(self, name): - import imp - return imp.load_source('my_tmp', fullpath) - - def get_code(self, fullname): - return compile(Path(fullpath).read_text(), "foo", "exec") - - def is_package(self, __): - return False + import importlib.abc + import importlib.util + + class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): + def find_spec(self, fullname, path, target=None): + if fullname == "my_tmp": + return importlib.util.spec_from_loader(fullname, self) + + def get_filename(self, fullname): + if fullname != "my_tmp": + raise ImportError(f"unexpected module name '{fullname}'") + return fullpath + + def get_data(self, path): + if not Path(path).samefile(fullpath): + raise OSError(f"expected path '{fullpath}', got '{path}'") + return Path(fullpath).read_text() sys.meta_path.insert(0, MyTempImporter()) From d71d42658298e9ec766be04adb29eebf3554d134 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 02:11:23 +0300 Subject: [PATCH 1709/3726] ShimImporter: implement modern interface --- IPython/utils/shimmodule.py | 35 ++++++++------------------ IPython/utils/tests/test_shimmodule.py | 4 +++ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/IPython/utils/shimmodule.py b/IPython/utils/shimmodule.py index cc05503e825..ec243a03429 100644 --- a/IPython/utils/shimmodule.py +++ b/IPython/utils/shimmodule.py @@ -3,6 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import importlib.abc +import importlib.util import sys import types from importlib import import_module @@ -13,41 +15,26 @@ class ShimWarning(Warning): """A warning to show when a module has moved, and a shim is in its place.""" -class ShimImporter(object): + +class ShimImporter(importlib.abc.MetaPathFinder): """Import hook for a shim. - + This ensures that submodule imports return the real target module, not a clone that will confuse `is` and `isinstance` checks. """ def __init__(self, src, mirror): self.src = src self.mirror = mirror - + def _mirror_name(self, fullname): """get the name of the mirrored module""" - - return self.mirror + fullname[len(self.src):] - def find_module(self, fullname, path=None): - """Return self if we should be used to import the module.""" - if fullname.startswith(self.src + '.'): - mirror_name = self._mirror_name(fullname) - try: - mod = import_item(mirror_name) - except ImportError: - return - else: - if not isinstance(mod, types.ModuleType): - # not a module - return None - return self + return self.mirror + fullname[len(self.src) :] - def load_module(self, fullname): - """Import the mirrored module, and insert it into sys.modules""" - mirror_name = self._mirror_name(fullname) - mod = import_item(mirror_name) - sys.modules[fullname] = mod - return mod + def find_spec(self, fullname, path, target=None): + if fullname.startswith(self.src + "."): + mirror_name = self._mirror_name(fullname) + return importlib.util.find_spec(mirror_name) class ShimModule(types.ModuleType): diff --git a/IPython/utils/tests/test_shimmodule.py b/IPython/utils/tests/test_shimmodule.py index 814e48240a9..30f2ffa09f2 100644 --- a/IPython/utils/tests/test_shimmodule.py +++ b/IPython/utils/tests/test_shimmodule.py @@ -8,3 +8,7 @@ def test_shim_warning(): sys.modules.pop('IPython.config', None) with pytest.warns(ShimWarning): import IPython.config + + import traitlets.config + + assert IPython.config.Config is traitlets.config.Config From c2ae085713ac810a3dc72807f5c61b60b0d496d8 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 03:56:04 +0300 Subject: [PATCH 1710/3726] ImportDenier: implement modern interface --- IPython/external/qt_loaders.py | 11 +++++------ IPython/external/tests/__init__.py | 0 IPython/external/tests/test_qt_loaders.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 IPython/external/tests/__init__.py create mode 100644 IPython/external/tests/test_qt_loaders.py diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 982590e6b10..3351e880bcb 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -8,6 +8,7 @@ This is used primarily by qt and qt_for_kernel, and shouldn't be accessed directly from the outside """ +import importlib.abc import sys import types from functools import partial, lru_cache @@ -47,7 +48,7 @@ } -class ImportDenier(object): +class ImportDenier(importlib.abc.MetaPathFinder): """Import Hook that will guard against bad Qt imports once IPython commits to a specific binding """ @@ -59,14 +60,12 @@ def forbid(self, module_name): sys.modules.pop(module_name, None) self.__forbidden.add(module_name) - def find_module(self, fullname, path=None): + def find_spec(self, fullname, path, target=None): if path: return if fullname in self.__forbidden: - return self - - def load_module(self, fullname): - raise ImportError(""" + raise ImportError( + """ Importing %s disabled by IPython, which has already imported an Incompatible QT Binding: %s """ % (fullname, loaded_api())) diff --git a/IPython/external/tests/__init__.py b/IPython/external/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/IPython/external/tests/test_qt_loaders.py b/IPython/external/tests/test_qt_loaders.py new file mode 100644 index 00000000000..7bc9ccfa86d --- /dev/null +++ b/IPython/external/tests/test_qt_loaders.py @@ -0,0 +1,11 @@ +import importlib +import pytest +from IPython.external.qt_loaders import ID + + +def test_import_denier(): + ID.forbid("ipython_denied_module") + with pytest.raises(ImportError, match="disabled by IPython"): + import ipython_denied_module + with pytest.raises(ImportError, match="disabled by IPython"): + importlib.import_module("ipython_denied_module") From 27b007b338003f9fa9e8ef381c33fb48362b5032 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 17:52:55 +0300 Subject: [PATCH 1711/3726] test_py_script_file_attribute_interactively: Coverage fix --- IPython/core/tests/test_shellapp.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index f9ea57825be..32a6aee7238 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -50,8 +50,4 @@ def test_py_script_file_attribute_interactively(self): out, err = tt.ipexec(self.fname, options=['-i'], commands=['"__file__" in globals()', 'print(123)', 'exit()']) - if 'False' not in out: - print("Subprocess stderr:") - print(err) - print('-----') - raise AssertionError("'False' not found in %r" % out) + assert "False" in out, f"Subprocess stderr:\n{err}\n-----" From 5e3d218fe03271cb4daebf16dd1cedd3148e7cb2 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 17:53:06 +0300 Subject: [PATCH 1712/3726] darker --- IPython/core/tests/test_shellapp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index 32a6aee7238..9f4f87b81f2 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -48,6 +48,9 @@ def test_py_script_file_attribute_interactively(self): src = "True\n" self.mktmp(src) - out, err = tt.ipexec(self.fname, options=['-i'], - commands=['"__file__" in globals()', 'print(123)', 'exit()']) + out, err = tt.ipexec( + self.fname, + options=["-i"], + commands=['"__file__" in globals()', "print(123)", "exit()"], + ) assert "False" in out, f"Subprocess stderr:\n{err}\n-----" From 420a29d8b78081aa17114f969a8983e375fd07c2 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 18:15:44 +0300 Subject: [PATCH 1713/3726] Enable `check_make_token_by_line_never_ends_empty` test It was added in 28a8ea639a7a37bfb03ee87d397ea5e69dcc537e but missed `test_` prefix in the name. Note: There is a type mismatch in calls to `make_tokens_by_line` but that does not lead to an error. --- IPython/core/tests/test_inputtransformer2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index 2a307d54d13..b00d1a8fb70 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -143,7 +143,8 @@ def null_cleanup_transformer(lines): """ return [] -def check_make_token_by_line_never_ends_empty(): + +def test_check_make_token_by_line_never_ends_empty(): """ Check that not sequence of single or double characters ends up leading to en empty list of tokens """ From b221a8fb67e9db705de35c935858ac5f4531f0de Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 18:27:40 +0300 Subject: [PATCH 1714/3726] test_inputsplitter: remove unused function Leftover from 358332df835afb918bdfe4c8d87ec4fc70d00c25 --- IPython/core/tests/test_inputsplitter.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 0a13fb68c85..46c804aecda 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -49,10 +49,6 @@ def mini_interactive_loop(input_func): # Test utilities, just for local use #----------------------------------------------------------------------------- -def assemble(block): - """Assemble a block into multi-line sub-blocks.""" - return ['\n'.join(sub_block)+'\n' for sub_block in block] - def pseudo_input(lines): """Return a function that acts like raw_input but feeds the input list.""" From b00e8a7d1ecb70e72361de52d50b816989fa9278 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 19:23:25 +0300 Subject: [PATCH 1715/3726] test_path: revive match tests These tests had not been running on Nose and I did not spot that previously. --- IPython/utils/tests/test_path.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index db5f1c879ba..e846b584e19 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -390,7 +390,7 @@ def test_match_posix(self): ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), ([r'a\[012]'], ['a[012]']), ]: - yield (self.check_match, patterns, matches) + self.check_match(patterns, matches) @skip_if_not_win32 def test_match_windows(self): @@ -401,7 +401,7 @@ def test_match_windows(self): ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), ([r'a\[012]'], [r'a\[012]']), ]: - yield (self.check_match, patterns, matches) + self.check_match(patterns, matches) # TODO : pytest.mark.parametrise once nose is gone. From e8d5d4b18c0f0ded27647051e707abe5aeb811ea Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 19:42:34 +0300 Subject: [PATCH 1716/3726] CI: Install texlive to run latex tests --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9bd353a97e4..b2bbf9c4601 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,9 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Install latex + if: runner.os == 'Linux' + run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng - name: Install and update Python dependencies run: | python -m pip install --upgrade pip setuptools wheel From 044a51b3b20ce726d6d67e7a6289623593a4d37c Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 17 Nov 2021 22:10:39 +0300 Subject: [PATCH 1717/3726] Deprecate `IPython.utils.version` The `distutils.version.LooseVersion` is deprecated and there is no replacement in the standard library. --- IPython/external/qt_for_kernel.py | 5 ++--- IPython/external/qt_loaders.py | 7 +++---- IPython/utils/version.py | 18 +++++++++++------- pytest.ini | 1 + 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/IPython/external/qt_for_kernel.py b/IPython/external/qt_for_kernel.py index d2e7bd99f01..b3168f6e2e0 100644 --- a/IPython/external/qt_for_kernel.py +++ b/IPython/external/qt_for_kernel.py @@ -31,7 +31,6 @@ import os import sys -from IPython.utils.version import check_version from IPython.external.qt_loaders import ( load_qt, loaded_api, @@ -101,8 +100,8 @@ def get_options(): mpl = sys.modules.get('matplotlib', None) - if mpl is not None and not check_version(mpl.__version__, '1.0.2'): - #1.0.1 only supports PyQt4 v1 + if mpl is not None and tuple(mpl.__version__.split(".")) < ("1", "0", "2"): + # 1.0.1 only supports PyQt4 v1 return [QT_API_PYQT_DEFAULT] qt_api = os.environ.get('QT_API', None) diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 982590e6b10..c563b587d62 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -13,8 +13,6 @@ from functools import partial, lru_cache import operator -from IPython.utils.version import check_version - # ### Available APIs. # Qt6 QT_API_PYQT6 = "pyqt6" @@ -149,7 +147,8 @@ def has_binding(api): if api == QT_API_PYSIDE: # We can also safely check PySide version import PySide - return check_version(PySide.__version__, '1.0.3') + + return PySide.__version_info__ >= (1, 0, 3) return True @@ -211,7 +210,7 @@ def import_pyqt4(version=2): from PyQt4 import QtGui, QtCore, QtSvg - if not check_version(QtCore.PYQT_VERSION_STR, '4.7'): + if QtCore.PYQT_VERSION < 0x040700: raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % QtCore.PYQT_VERSION_STR) diff --git a/IPython/utils/version.py b/IPython/utils/version.py index 1de0047e6b4..050155640d4 100644 --- a/IPython/utils/version.py +++ b/IPython/utils/version.py @@ -12,15 +12,10 @@ # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +from warnings import warn -from distutils.version import LooseVersion +warn("The `IPython.utils.version` module has been deprecated since IPython 8.0.") -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- def check_version(v, check): """check version string v >= check @@ -29,6 +24,15 @@ def check_version(v, check): it is assumed that the dependency is satisfied. Users on dev branches are responsible for keeping their own packages up to date. """ + warn( + "`check_version` function is deprecated as of IPython 8.0" + "and will be removed in future versions.", + DeprecationWarning, + stacklevel=2, + ) + + from distutils.version import LooseVersion + try: return LooseVersion(v) >= LooseVersion(check) except TypeError: diff --git a/pytest.ini b/pytest.ini index 3aeec55e754..9189cf6c33b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -43,5 +43,6 @@ addopts = --durations=10 --ignore=IPython/utils/log.py --ignore=IPython/utils/signatures.py --ignore=IPython/utils/traitlets.py + --ignore=IPython/utils/version.py doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS From 0e59ae55629f08fd4e129e3678a59318b61f42e7 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 22:05:05 +0300 Subject: [PATCH 1718/3726] Rewrite bunch of `raise AssertionError` and `assert False` tests --- IPython/core/tests/test_compilerop.py | 8 +++---- IPython/core/tests/test_debugger.py | 6 ++---- IPython/core/tests/test_hooks.py | 8 +++---- IPython/core/tests/test_logger.py | 6 +----- IPython/core/tests/test_magic.py | 15 ++++++------- IPython/utils/tests/test_text.py | 31 +++++++++++++++------------ 6 files changed, 32 insertions(+), 42 deletions(-) diff --git a/IPython/core/tests/test_compilerop.py b/IPython/core/tests/test_compilerop.py index aa531b81d44..b939bb60676 100644 --- a/IPython/core/tests/test_compilerop.py +++ b/IPython/core/tests/test_compilerop.py @@ -66,8 +66,6 @@ def test_compiler_check_cache(): cp.cache('x=1', 99) # Ensure now that after clearing the cache, our entries survive linecache.checkcache() - for k in linecache.cache: - if k.startswith(' displaywidth: - print("Columnize displayed something lager than displaywidth : %s " % longer_line) - print("longer element : %s " % longer_element) - print("displaywidth : %s " % displaywidth) - print("number of element : %s " % nitems) - print("size of each element :\n %s" % rand_len) - assert False, "row_first={0}".format(row_first) + assert longer_line <= displaywidth, ( + f"Columnize displayed something lager than displaywidth : {longer_line}\n" + f"longer element : {longer_element}\n" + f"displaywidth : {displaywidth}\n" + f"number of element : {nitems}\n" + f"size of each element : {rand_len}\n" + f"row_first={row_first}\n" + ) # TODO: pytest mark.parametrize once nose removed. @@ -103,9 +104,9 @@ def eval_formatter_check(f): ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"café", b="café") s = f.format("{n} {n//4} {stuff.split()[0]}", **ns) assert s == "12 3 hello" - s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns) + s = f.format(" ".join(["{n//%i}" % i for i in range(1, 8)]), **ns) assert s == "12 6 4 3 2 2 1" - s = f.format('{[n//i for i in range(1,8)]}', **ns) + s = f.format("{[n//i for i in range(1,8)]}", **ns) assert s == "[12, 6, 4, 3, 2, 2, 1]" s = f.format("{stuff!s}", **ns) assert s == ns["stuff"] @@ -133,9 +134,9 @@ def eval_formatter_slicing_check(f): pytest.raises(SyntaxError, f.format, "{n:x}", **ns) def eval_formatter_no_slicing_check(f): - ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) - - s = f.format('{n:x} {pi**2:+f}', **ns) + ns = dict(n=12, pi=math.pi, stuff="hello there", os=os) + + s = f.format("{n:x} {pi**2:+f}", **ns) assert s == "c +9.869604" s = f.format("{stuff[slice(1,4)]}", **ns) @@ -187,10 +188,11 @@ def test_strip_email(): def test_strip_email2(): - src = '> > > list()' - cln = 'list()' + src = "> > > list()" + cln = "list()" assert text.strip_email_quotes(src) == cln + def test_LSString(): lss = text.LSString("abc\ndef") assert lss.l == ["abc", "def"] @@ -198,6 +200,7 @@ def test_LSString(): lss = text.LSString(os.getcwd()) assert isinstance(lss.p[0], Path) + def test_SList(): sl = text.SList(["a 11", "b 1", "a 2"]) assert sl.n == "a 11\nb 1\na 2" From 7410af58c54f7356328a9311025853b1191c492f Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 23:04:44 +0300 Subject: [PATCH 1719/3726] Remove `skip_iptest_but_not_pytest` --- IPython/core/tests/test_inputtransformer2.py | 3 --- IPython/core/tests/test_magic.py | 6 ------ IPython/lib/tests/test_latextools.py | 4 ---- IPython/lib/tests/test_pretty.py | 2 -- IPython/testing/decorators.py | 11 ----------- IPython/utils/tests/test_capture.py | 5 ----- IPython/utils/tests/test_pycolorize.py | 4 ---- IPython/utils/tests/test_tokenutil.py | 2 -- 8 files changed, 37 deletions(-) diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index b00d1a8fb70..f29bb2af6dd 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -12,7 +12,6 @@ from IPython.core import inputtransformer2 as ipt2 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line -from IPython.testing.decorators import skip_iptest_but_not_pytest MULTILINE_MAGIC = ("""\ a = f() @@ -285,14 +284,12 @@ def __init__(self, s): ] -@skip_iptest_but_not_pytest @pytest.mark.parametrize("code, expected, number", examples) def test_check_complete_param(code, expected, number): cc = ipt2.TransformerManager().check_complete assert cc(code) == (expected, number) -@skip_iptest_but_not_pytest @pytest.mark.xfail( reason="Bug in python 3.9.8 – bpo 45738", condition=sys.version_info[:3] == (3, 9, 8), diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 0cadae6ef8d..95bd85b15e0 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -972,7 +972,6 @@ def event_loop(): yield asyncio.get_event_loop_policy().get_event_loop() -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" @@ -986,7 +985,6 @@ def test_script_out(event_loop): assert ip.user_ns["output"] == "hi\n" -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" @@ -999,7 +997,6 @@ def test_script_err(event_loop): assert ip.user_ns["error"] == "hello\n" -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" @@ -1014,7 +1011,6 @@ def test_script_out_err(): assert ip.user_ns["error"] == "hello\n" -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" @@ -1027,7 +1023,6 @@ async def test_script_bg_out(event_loop): event_loop.stop() -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" @@ -1039,7 +1034,6 @@ async def test_script_bg_err(): ip.user_ns["error"].close() -@dec.skip_iptest_but_not_pytest @dec.skip_win32 @pytest.mark.skipif( sys.platform == "win32", reason="This test does not run under Windows" diff --git a/IPython/lib/tests/test_latextools.py b/IPython/lib/tests/test_latextools.py index 60a7589b508..ead73abff93 100644 --- a/IPython/lib/tests/test_latextools.py +++ b/IPython/lib/tests/test_latextools.py @@ -11,13 +11,11 @@ from IPython.testing.decorators import ( onlyif_cmds_exist, skipif_not_matplotlib, - skip_iptest_but_not_pytest, ) from IPython.utils.process import FindCmdError @pytest.mark.parametrize('command', ['latex', 'dvipng']) -@skip_iptest_but_not_pytest def test_check_latex_to_png_dvipng_fails_when_no_cmd(command): def mock_find_cmd(arg): if arg == command: @@ -32,7 +30,6 @@ def no_op(*args, **kwargs): yield -@skip_iptest_but_not_pytest @onlyif_cmds_exist("latex", "dvipng") @pytest.mark.parametrize("s, wrap", [(u"$$x^2$$", False), (u"x^2", True)]) def test_latex_to_png_dvipng_runs(s, wrap): @@ -60,7 +57,6 @@ def patch_latextool(mock=mock_kpsewhich): @pytest.mark.parametrize('context', [no_op, patch_latextool]) @pytest.mark.parametrize('s_wrap', [("$x^2$", False), ("x^2", True)]) -@skip_iptest_but_not_pytest def test_latex_to_png_mpl_runs(s_wrap, context): """ Test that latex_to_png_mpl just runs without error. diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index ec1211ac195..8b8a6ee75f6 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -16,7 +16,6 @@ import pytest from IPython.lib import pretty -from IPython.testing.decorators import skip_iptest_but_not_pytest from io import StringIO @@ -129,7 +128,6 @@ def test_callability_checking(): ], ), ) -@skip_iptest_but_not_pytest def test_sets(obj, expected_output): """ Test that set and frozenset use Python 3 formatting. diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 74af7c1c83d..93f55d96806 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -149,17 +149,6 @@ def decor(f): return decor -def skip_iptest_but_not_pytest(f): - """ - Warning this will make the test invisible to iptest. - """ - import os - - if os.environ.get("IPTEST_WORKING_DIR", None) is not None: - f.__test__ = False - return f - - def skipif(skip_condition, msg=None): """Make function raise SkipTest exception if skip_condition is true diff --git a/IPython/utils/tests/test_capture.py b/IPython/utils/tests/test_capture.py index 3cfa8276acf..8645ed18c9e 100644 --- a/IPython/utils/tests/test_capture.py +++ b/IPython/utils/tests/test_capture.py @@ -17,8 +17,6 @@ import pytest -from IPython.testing.decorators import skip_iptest_but_not_pytest - from IPython.utils import capture #----------------------------------------------------------------------------- @@ -69,7 +67,6 @@ # Test Functions #----------------------------------------------------------------------------- @pytest.mark.parametrize("method_mime", _mime_map.items()) -@skip_iptest_but_not_pytest def test_rich_output_empty(method_mime): """RichOutput with no args""" rich = capture.RichOutput() @@ -88,7 +85,6 @@ def test_rich_output(): assert rich._repr_svg_() is None -@skip_iptest_but_not_pytest @pytest.mark.parametrize("method_mime", _mime_map.items()) def test_rich_output_no_metadata(method_mime): """test RichOutput with no metadata""" @@ -98,7 +94,6 @@ def test_rich_output_no_metadata(method_mime): assert getattr(rich, method)() == data[mime] -@skip_iptest_but_not_pytest @pytest.mark.parametrize("method_mime", _mime_map.items()) def test_rich_output_metadata(method_mime): """test RichOutput with metadata""" diff --git a/IPython/utils/tests/test_pycolorize.py b/IPython/utils/tests/test_pycolorize.py index 178f4a98c47..05d51b6e44d 100644 --- a/IPython/utils/tests/test_pycolorize.py +++ b/IPython/utils/tests/test_pycolorize.py @@ -17,8 +17,6 @@ # Imports #----------------------------------------------------------------------------- -from IPython.testing.decorators import skip_iptest_but_not_pytest - # our own from IPython.utils.PyColorize import Parser import io @@ -53,7 +51,6 @@ def __init__(self): """ -@skip_iptest_but_not_pytest def test_parse_sample(style): """and test writing to a buffer""" buf = io.StringIO() @@ -65,7 +62,6 @@ def test_parse_sample(style): assert "ERROR" not in f1 -@skip_iptest_but_not_pytest def test_parse_error(style): p = Parser(style=style) f1 = p.format(")", "str") diff --git a/IPython/utils/tests/test_tokenutil.py b/IPython/utils/tests/test_tokenutil.py index 406c0ae51fa..c4539d1fc7e 100644 --- a/IPython/utils/tests/test_tokenutil.py +++ b/IPython/utils/tests/test_tokenutil.py @@ -3,7 +3,6 @@ # Distributed under the terms of the Modified BSD License. import pytest -from IPython.testing.decorators import skip_iptest_but_not_pytest from IPython.utils.tokenutil import token_at_cursor, line_at_cursor @@ -132,7 +131,6 @@ def test_line_at_cursor(): ["int"] * (22 - 16) + ["map"] * (28 - 22), ), ) -@skip_iptest_but_not_pytest def test_multiline_statement(c, token): cell = """a = (1, 3) From daadb7c2e34e62ebf8a3aec9621f2969d391e076 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 23:12:01 +0300 Subject: [PATCH 1720/3726] Remove bunch of deprecated and unused testing decorators --- IPython/testing/decorators.py | 119 ---------------------------------- 1 file changed, 119 deletions(-) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 74af7c1c83d..63a1063aaa0 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -61,93 +61,6 @@ def test(self): # Utility functions -def apply_wrapper(wrapper, func): - """Apply a wrapper to a function for decoration. - - This mixes Michele Simionato's decorator tool with nose's make_decorator, - to apply a wrapper in a decorator so that all nose attributes, as well as - function signature and other properties, survive the decoration cleanly. - This will ensure that wrapped functions can still be well introspected via - IPython, for example. - """ - warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0", - DeprecationWarning, stacklevel=2) - import nose.tools - - return decorator(wrapper,nose.tools.make_decorator(func)(wrapper)) - - -def make_label_dec(label, ds=None): - """Factory function to create a decorator that applies one or more labels. - - Parameters - ---------- - label : string or sequence - One or more labels that will be applied by the decorator to the functions - it decorates. Labels are attributes of the decorated function with their - value set to True. - - ds : string - An optional docstring for the resulting decorator. If not given, a - default docstring is auto-generated. - - Returns - ------- - A decorator. - - Examples - -------- - - A simple labeling decorator: - - >>> slow = make_label_dec('slow') - >>> slow.__doc__ - "Labels a test as 'slow'." - - And one that uses multiple labels and a custom docstring: - - >>> rare = make_label_dec(['slow','hard'], - ... "Mix labels 'slow' and 'hard' for rare tests.") - >>> rare.__doc__ - "Mix labels 'slow' and 'hard' for rare tests." - - Now, let's test using this one: - >>> @rare - ... def f(): pass - ... - >>> - >>> f.slow - True - >>> f.hard - True - """ - - warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0", - DeprecationWarning, stacklevel=2) - if isinstance(label, str): - labels = [label] - else: - labels = label - - # Validate that the given label(s) are OK for use in setattr() by doing a - # dry run on a dummy function. - tmp = lambda : None - for label in labels: - setattr(tmp,label,True) - - # This is the actual decorator we'll return - def decor(f): - for label in labels: - setattr(f,label,True) - return f - - # Apply the user's docstring, or autogenerate a basic one - if ds is None: - ds = "Labels a test as %r." % label - decor.__doc__ = ds - - return decor - def skip_iptest_but_not_pytest(f): """ @@ -234,20 +147,6 @@ def module_not_available(module): return mod_not_avail -def decorated_dummy(dec, name): - """Return a dummy function decorated with dec, with the given name. - - Examples - -------- - import IPython.testing.decorators as dec - setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__) - """ - warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0", - DeprecationWarning, stacklevel=2) - dummy = lambda: None - dummy.__name__ = name - return dec(dummy) - #----------------------------------------------------------------------------- # Decorators for public use @@ -279,12 +178,6 @@ def decorated_dummy(dec, name): skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt') -# not a decorator itself, returns a dummy function to be used as setup -def skip_file_no_x11(name): - warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0", - DeprecationWarning, stacklevel=2) - return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None - # Other skip decorators # generic skip without module @@ -328,15 +221,3 @@ def onlyif_cmds_exist(*commands): return pytest.mark.skip(reason=reason) return null_deco - -def onlyif_any_cmd_exists(*commands): - """ - Decorator to skip test unless at least one of `commands` is found. - """ - warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0", - DeprecationWarning, stacklevel=2) - for cmd in commands: - if shutil.which(cmd): - return null_deco - return skip("This test runs only if one of the commands {0} " - "is installed".format(commands)) From 6b1846f57ca3db5e32e32605335b0f3b5c5d97be Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Thu, 18 Nov 2021 23:33:18 +0300 Subject: [PATCH 1721/3726] Cleanup ipdoctest Leftovers after Nose plugin removal. --- IPython/testing/plugin/ipdoctest.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 784760f81cf..d5c794b80ff 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -38,14 +38,6 @@ # Classes and functions #----------------------------------------------------------------------------- -def is_extension_module(filename): - """Return whether the given filename is an extension module. - - This simply checks that the extension is either .so or .pyd. - """ - return os.path.splitext(filename)[1].lower() in ('.so','.pyd') - - class DocTestSkip(object): """Object wrapper for doctests to be skipped.""" @@ -443,10 +435,3 @@ def run(self, test, compileflags=None, out=None, clear_globs=True): with modified_env({'COLUMNS': '80', 'LINES': '24'}): return super(IPDocTestRunner,self).run(test, compileflags,out,clear_globs) - - -class DocFileCase(doctest.DocFileCase): - """Overrides to provide filename - """ - def address(self): - return (self._dt_test.filename, None, None) From ccdbaf50e48619dee0401ec68977dd6376e16d20 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 1 Nov 2021 20:20:27 +0300 Subject: [PATCH 1722/3726] CI: Collect coverage from docs build --- .github/workflows/docs.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 38b6ab9ec5d..de9b70a7058 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,9 +18,18 @@ jobs: sudo apt-get install graphviz - name: Install Python dependencies run: | - python -m pip install --upgrade pip setuptools + python -m pip install --upgrade pip setuptools coverage pip install -r docs/requirements.txt - name: Build docs run: | python tools/fixup_whats_new_pr.py - make -C docs/ html SPHINXOPTS="-W" + make -C docs/ html SPHINXOPTS="-W" \ + PYTHON="coverage run -a" \ + SPHINXBUILD="coverage run -a -m sphinx.cmd.build" + - name: Generate coverage xml + run: | + coverage combine `find . -name .coverage\*` && coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + name: Docs From cd387ef61715f61c6ea71f0cfa58ecab5a8a7f78 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 19 Nov 2021 04:20:36 +0300 Subject: [PATCH 1723/3726] 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. --- IPython/core/magic.py | 13 ++-- IPython/core/magics/basic.py | 1 + IPython/core/magics/config.py | 79 ++++++++++++++------- IPython/core/magics/script.py | 2 + IPython/core/tests/test_interactiveshell.py | 26 ++++--- IPython/extensions/storemagic.py | 3 + IPython/terminal/magics.py | 2 + 7 files changed, 84 insertions(+), 42 deletions(-) diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 6dc0ad34dde..7f7f1f94b28 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -20,7 +20,6 @@ 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 magic_deco(arg): 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 72cfc804143..4a8f223c206 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 b582bdd6eac..25a74e041ab 100644 --- a/IPython/core/magics/config.py +++ b/IPython/core/magics/config.py @@ -54,44 +54,73 @@ def config(self, s): 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= - Current: 2 - Choices: (0, 1, 2) - Instruct the completer to omit private method names - Specifically, when completing on ``object.``. - 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= + IPCompleter(Completer) options + ---------------------------- + IPCompleter.backslash_combining_completions= + Enable unicode completions, e.g. \\alpha . 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__= + IPCompleter.debug= + Enable debug for the Completer. Mostly print extra information for + experimental jedi integration. + Current: False + IPCompleter.greedy= + 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= + 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__= + DEPRECATED as of version 5.0. Instruct the completer to use __all__ for the completion Specifically, when completing on ``object.``. When True: only those names in obj.__all__ will be included. When False [default]: the __all__ attribute is ignored - IPCompleter.greedy= 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= + 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= + Instruct the completer to omit private method names + Specifically, when completing on ``object.``. + 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= + If True, emit profiling data for completion subsystem using cProfile. + Current: False + IPCompleter.profiler_output_dir= + Template for path at which to output profile data for completions. + Current: '.completion_profiles' + IPCompleter.use_jedi= + 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 @@ def config(self, s): # 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 b9f3b8fb157..7876e648674 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -304,6 +304,8 @@ async def _stream_communicate(process, cell): # 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 f2185e45e76..a3a55fc66af 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -346,10 +346,15 @@ def lmagic(line): "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 @@ def cmagic(line, cell): "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 9102ea1c20c..b4a8cf82442 100644 --- a/IPython/extensions/storemagic.py +++ b/IPython/extensions/storemagic.py @@ -17,6 +17,7 @@ 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 @@ def __init__(self, shell): 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 @@ def store(self, parameter_s=''): 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 c5d382e82a3..38842231f0d 100644 --- a/IPython/terminal/magics.py +++ b/IPython/terminal/magics.py @@ -11,6 +11,7 @@ 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 @@ def autoindent(self, parameter_s = ''): 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. From 51151144348f143051b12d88fe23cedadb866626 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 20 Nov 2021 22:19:51 +0300 Subject: [PATCH 1724/3726] Remove unused `requests` from test requirements --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index c05a769a0ea..a96b5473f27 100755 --- a/setup.py +++ b/setup.py @@ -176,7 +176,6 @@ doc=["Sphinx>=1.3"], test=[ "pytest", - "requests", "testpath", "pygments", "nbformat", From eac76a508587b0d95d6c55f6db58c5e2feb20f5d Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 20 Nov 2021 22:37:30 +0300 Subject: [PATCH 1725/3726] Move optional dependencies to a separate set --- .github/workflows/test.yml | 15 ++++++++++----- IPython/core/tests/test_magic.py | 1 + IPython/core/tests/test_run.py | 2 ++ IPython/lib/display.py | 3 +++ IPython/terminal/tests/test_help.py | 2 ++ appveyor.yml | 4 ++-- setup.py | 9 +++++++++ 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2bbf9c4601..c40291316b9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,12 +16,19 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.7", "3.8", "3.9", "3.10"] + deps: [test_extra] # Test all on ubuntu, test ends on macos include: - os: macos-latest python-version: "3.7" + deps: test_extra - os: macos-latest python-version: "3.10" + deps: test_extra + # Tests minimal dependencies set + - os: ubuntu-latest + python-version: "3.10" + deps: test steps: - uses: actions/checkout@v2 @@ -30,15 +37,13 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install latex - if: runner.os == 'Linux' + if: runner.os == 'Linux' && matrix.deps == 'test_extra' run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng - name: Install and update Python dependencies run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade -e file://$PWD#egg=ipython[test] - python -m pip install --upgrade --upgrade-strategy eager trio curio - python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' pandas - python -m pip install --upgrade check-manifest pytest-cov anyio + python -m pip install --upgrade -e .[${{ matrix.deps }}] + python -m pip install --upgrade check-manifest pytest-cov - name: Check manifest run: check-manifest - name: pytest diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 0cadae6ef8d..3d8a37bf3a6 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -742,6 +742,7 @@ def test_extension(): def test_notebook_export_json(): + pytest.importorskip("nbformat") _ip = get_ipython() _ip.history_manager.reset() # Clear any existing history. cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index a8dfbd0e3c1..a872f5a6620 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -381,6 +381,7 @@ def test_ignore_sys_exit(self): def test_run_nb(self): """Test %run notebook.ipynb""" + pytest.importorskip("nbformat") from nbformat import v4, writes nb = v4.new_notebook( cells=[ @@ -397,6 +398,7 @@ def test_run_nb(self): def test_run_nb_error(self): """Test %run notebook.ipynb error""" + pytest.importorskip("nbformat") from nbformat import v4, writes # %run when a file name isn't provided pytest.raises(Exception, _ip.magic, "run") diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 33d70127a71..57ae8deb3e8 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -66,6 +66,9 @@ class Audio(DisplayObject): Examples -------- + >>> import pytest + >>> np = pytest.importorskip("numpy") + Generate a sound >>> import numpy as np diff --git a/IPython/terminal/tests/test_help.py b/IPython/terminal/tests/test_help.py index ec5bf6f9f62..038f560b2fb 100644 --- a/IPython/terminal/tests/test_help.py +++ b/IPython/terminal/tests/test_help.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import pytest import IPython.testing.tools as tt @@ -25,4 +26,5 @@ def test_locate_profile_help(): tt.help_all_output_test("locate profile") def test_trust_help(): + pytest.importorskip("nbformat") tt.help_all_output_test("trust") diff --git a/appveyor.yml b/appveyor.yml index 10659d8e8b1..b524356f52e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,8 +31,8 @@ init: install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install --upgrade setuptools pip - - pip install pytest pytest-cov pytest-trio matplotlib pandas - - pip install -e .[test] + - pip install pytest-cov + - pip install -e .[test_extra] test_script: - pytest --color=yes -ra --cov --cov-report=xml on_finish: diff --git a/setup.py b/setup.py index a96b5473f27..ce8e1682cb5 100755 --- a/setup.py +++ b/setup.py @@ -178,9 +178,18 @@ "pytest", "testpath", "pygments", + ], + test_extra=[ + "pytest", + "testpath", + "curio", + "matplotlib!=3.2.0", "nbformat", "ipykernel", "numpy>=1.17", + "pandas", + "pygments", + "trio", ], terminal=[], kernel=["ipykernel"], From 227d51b897a5be3c14b1e835fa5336fd7eddc864 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 20 Nov 2021 23:42:59 +0300 Subject: [PATCH 1726/3726] Python 3.11 fixes --- .github/workflows/test.yml | 4 ++++ IPython/core/tests/test_completer.py | 9 +++++++-- IPython/core/tests/test_inputsplitter.py | 9 +++++++-- IPython/core/tests/test_inputtransformer2.py | 7 +++++-- IPython/core/tests/test_oinspect.py | 5 ++--- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c40291316b9..d9528f007ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,10 @@ jobs: - os: ubuntu-latest python-version: "3.10" deps: test + # Tests latest development Python version + - os: ubuntu-latest + python-version: "3.11-dev" + deps: test steps: - uses: actions/checkout@v2 diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 47071507933..c470b1fe723 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -104,7 +104,7 @@ def test_unicode_range(): assert len_exp == len_test, message # fail if new unicode symbols have been added. - assert len_exp <= 137714, message + assert len_exp <= 138552, message @contextmanager @@ -219,6 +219,11 @@ def _ipython_key_completions_(self): return list(self.things) +@pytest.mark.xfail( + sys.version_info >= (3, 11), + reason="parso does not support 3.11 yet", + raises=NotImplementedError, +) class TestCompleter(unittest.TestCase): def setUp(self): """ @@ -282,7 +287,7 @@ def test_latex_completions(self): ip = get_ipython() # Test some random unicode symbols - keys = random.sample(latex_symbols.keys(), 10) + keys = random.sample(sorted(latex_symbols), 10) for k in keys: text, matches = ip.complete(k) self.assertEqual(text, k) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 46c804aecda..ec000a2ae61 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -6,13 +6,13 @@ # Distributed under the terms of the Modified BSD License. import unittest +import pytest import sys from IPython.core import inputsplitter as isp from IPython.core.inputtransformer import InputTransformer from IPython.core.tests.test_inputtransformer import syntax, syntax_ml from IPython.testing import tools as tt -from IPython.testing.decorators import skipif #----------------------------------------------------------------------------- # Semi-complete examples (also used as tests) @@ -318,7 +318,12 @@ def test_unicode(self): self.isp.push(u'\xc3\xa9') self.isp.push(u"u'\xc3\xa9'") - @skipif(sys.version_info[:3] == (3, 9, 8)) + @pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, + ) def test_line_continuation(self): """ Test issue #2108.""" isp = self.isp diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index b00d1a8fb70..4a2306a0b64 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -276,7 +276,8 @@ def __init__(self, s): None, marks=pytest.mark.xfail( reason="Bug in python 3.9.8 – bpo 45738", - condition=sys.version_info[:3] == (3, 9, 8), + condition=sys.version_info + in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], raises=SystemError, strict=True, ), @@ -295,7 +296,9 @@ def test_check_complete_param(code, expected, number): @skip_iptest_but_not_pytest @pytest.mark.xfail( reason="Bug in python 3.9.8 – bpo 45738", - condition=sys.version_info[:3] == (3, 9, 8), + condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, ) def test_check_complete(): cc = ipt2.TransformerManager().check_complete diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index 7557fcf804b..58d07db8030 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -269,7 +269,6 @@ def test_empty_property_has_no_source(): def test_property_sources(): - import posixpath # A simple adder whose source and signature stays # the same across Python distributions def simple_add(a, b): @@ -283,7 +282,7 @@ def foo(self): foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) - dname = property(posixpath.dirname) + dname = property(oinspect.getdoc) adder = property(simple_add) i = inspector.info(A.foo, detail_level=1) @@ -291,7 +290,7 @@ def foo(self): assert "lambda self, v:" in i["source"] i = inspector.info(A.dname, detail_level=1) - assert "def dirname(p)" in i["source"] + assert "def getdoc(obj)" in i["source"] i = inspector.info(A.adder, detail_level=1) assert "def simple_add(a, b)" in i["source"] From 03a05afaf6f977aff9b9af57d442e9cb4f2071ef Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 21 Nov 2021 01:50:29 +0300 Subject: [PATCH 1727/3726] Remove unneeded `ipykernel` tests requirement --- IPython/core/tests/test_display.py | 2 -- setup.py | 1 - 2 files changed, 3 deletions(-) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index c6e13b917c6..2abd8cb3d84 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -156,7 +156,6 @@ def _get_inline_config(): return InlineBackend.instance() -@dec.skip_without("ipykernel") @dec.skip_without("matplotlib") def test_set_matplotlib_close(): cfg = _get_inline_config() @@ -196,7 +195,6 @@ def test_set_matplotlib_formats(): assert Figure not in f -@dec.skip_without("ipykernel") @dec.skip_without("matplotlib") def test_set_matplotlib_formats_kwargs(): from matplotlib.figure import Figure diff --git a/setup.py b/setup.py index ce8e1682cb5..bc536a84d77 100755 --- a/setup.py +++ b/setup.py @@ -185,7 +185,6 @@ "curio", "matplotlib!=3.2.0", "nbformat", - "ipykernel", "numpy>=1.17", "pandas", "pygments", From 298a52dc64e442c10763b523e8309ad2917a740c Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 21 Nov 2021 02:32:40 +0300 Subject: [PATCH 1728/3726] test_inputsplitter: suppress self-deprecation warning --- IPython/core/tests/test_inputsplitter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 46c804aecda..0b0a15cd533 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -6,9 +6,11 @@ # Distributed under the terms of the Modified BSD License. import unittest +import pytest import sys -from IPython.core import inputsplitter as isp +with pytest.warns(DeprecationWarning, match="inputsplitter"): + from IPython.core import inputsplitter as isp from IPython.core.inputtransformer import InputTransformer from IPython.core.tests.test_inputtransformer import syntax, syntax_ml from IPython.testing import tools as tt From a788e0c86455ca4f09e99f60d294512fc4ba0698 Mon Sep 17 00:00:00 2001 From: Maor Kleinberger Date: Sun, 21 Nov 2021 15:30:44 +0200 Subject: [PATCH 1729/3726] Use ThreadPoolExecutor for debugger cmdloop --- IPython/terminal/debugger.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 6ba73c7177c..1c4fb6ab413 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -1,11 +1,8 @@ import asyncio import os import sys -import threading from IPython.core.debugger import Pdb - - from IPython.core.completer import IPCompleter from .ptutils import IPythonPTCompleter from .shortcuts import create_ipython_shortcuts @@ -17,6 +14,7 @@ from prompt_toolkit.enums import EditingMode from prompt_toolkit.formatted_text import PygmentsTokens from prompt_toolkit.history import InMemoryHistory, FileHistory +from concurrent.futures import ThreadPoolExecutor from prompt_toolkit import __version__ as ptk_version PTK3 = ptk_version.startswith('3.') @@ -29,6 +27,7 @@ def __init__(self, *args, pt_session_options=None, **kwargs): Pdb.__init__(self, *args, **kwargs) self._ptcomp = None self.pt_init(pt_session_options) + self.thread_executor = ThreadPoolExecutor(1) def pt_init(self, pt_session_options=None): """Initialize the prompt session and the prompt loop @@ -110,7 +109,7 @@ def cmdloop(self, intro=None): if intro is not None: self.intro = intro if self.intro: - self.stdout.write(str(self.intro)+"\n") + print(self.intro, file=self.stdout) stop = None while not stop: if self.cmdqueue: @@ -120,24 +119,11 @@ def cmdloop(self, intro=None): self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals # Run the prompt in a different thread. - line = '' - keyboard_interrupt = False - - def in_thread(): - nonlocal line, keyboard_interrupt - try: - line = self.pt_app.prompt() - except EOFError: - line = 'EOF' - except KeyboardInterrupt: - keyboard_interrupt = True - - th = threading.Thread(target=in_thread) - th.start() - th.join() - - if keyboard_interrupt: - raise KeyboardInterrupt + try: + line = self.thread_executor.submit(self.pt_app.prompt).result() + except EOFError: + line = "EOF" + line = self.precmd(line) stop = self.onecmd(line) stop = self.postcmd(stop, line) From 323d9f476d717495ba55b5409172605b91c2470c Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Thu, 18 Nov 2021 15:49:15 -0800 Subject: [PATCH 1730/3726] Specify unwanted sections of info output --- IPython/core/interactiveshell.py | 3 ++- IPython/core/oinspect.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 0dec7535356..7d3d00300ea 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1804,7 +1804,7 @@ def object_inspect_text(self, oname, detail_level=0): """Get object info as formatted text""" return self.object_inspect_mime(oname, detail_level)['text/plain'] - def object_inspect_mime(self, oname, detail_level=0): + def object_inspect_mime(self, oname, detail_level=0, omit_sections={}): """Get object info as a mimebundle of formatted representations. A mimebundle is a dictionary, keyed by mime-type. @@ -1820,6 +1820,7 @@ def object_inspect_mime(self, oname, detail_level=0): info=info, detail_level=detail_level, formatter=docformat, + omit_sections=omit_sections, ) else: raise KeyError(oname) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 09b74700ba0..9153eb1f72e 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -566,7 +566,7 @@ def format_mime(self, bundle): bundle['text/plain'] = text return bundle - def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0): + def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0, omit_sections={}): """Retrieve an info dict and format it. Parameters @@ -581,6 +581,8 @@ def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0): already computed information detail_level: integer Granularity of detail level, if set to 1, give more information. + omit_sections: set[str] + Titles or keys to omit from output """ info = self._info(obj, oname=oname, info=info, detail_level=detail_level) @@ -591,6 +593,8 @@ def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0): } def append_field(bundle, title:str, key:str, formatter=None): + if title in omit_sections or key in omit_sections: + return field = info[key] if field is not None: formatted_field = self._mime_format(field, formatter) @@ -655,7 +659,7 @@ def code_formatter(text): return self.format_mime(_mime) - def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable_html_pager=True): + def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable_html_pager=True, omit_sections={}): """Show detailed information about an object. Optional arguments: @@ -676,8 +680,10 @@ def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable precomputed already. - detail_level: if set to 1, more information is given. + + - omit_sections: set of section keys and titles to omit """ - info = self._get_info(obj, oname, formatter, info, detail_level) + info = self._get_info(obj, oname, formatter, info, detail_level, omit_sections=omit_sections) if not enable_html_pager: del info['text/html'] page.page(info) From 50b543d805f5da1ab3e088abcc030eb64941df5b Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Tue, 23 Nov 2021 10:40:42 -0800 Subject: [PATCH 1731/3726] darker --- IPython/core/oinspect.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 9153eb1f72e..74b3cc9916a 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -566,7 +566,9 @@ def format_mime(self, bundle): bundle['text/plain'] = text return bundle - def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0, omit_sections={}): + def _get_info( + self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections={} + ): """Retrieve an info dict and format it. Parameters @@ -659,7 +661,16 @@ def code_formatter(text): return self.format_mime(_mime) - def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable_html_pager=True, omit_sections={}): + def pinfo( + self, + obj, + oname="", + formatter=None, + info=None, + detail_level=0, + enable_html_pager=True, + omit_sections={}, + ): """Show detailed information about an object. Optional arguments: @@ -683,7 +694,9 @@ def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable - omit_sections: set of section keys and titles to omit """ - info = self._get_info(obj, oname, formatter, info, detail_level, omit_sections=omit_sections) + info = self._get_info( + obj, oname, formatter, info, detail_level, omit_sections=omit_sections + ) if not enable_html_pager: del info['text/html'] page.page(info) From ba154eb9c6e5d519a648a5ea9d6082ef98128851 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 19 Nov 2021 19:45:40 +0300 Subject: [PATCH 1732/3726] Remove unneeded `skip_doctest` decorator usages --- IPython/core/debugger.py | 3 --- IPython/core/magics/execution.py | 1 - IPython/core/magics/osm.py | 7 ++----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 26aa94c1108..7163ae130d4 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -114,7 +114,6 @@ from IPython.utils import PyColorize from IPython.utils import coloransi, py3compat from IPython.core.excolors import exception_colors -from IPython.testing.skipdoctest import skip_doctest # skip module docstests __skip_doctest__ = True @@ -180,7 +179,6 @@ class Tracer(object): while functioning acceptably (though with limitations) if outside of it. """ - @skip_doctest def __init__(self, colors=None): """ DEPRECATED @@ -921,7 +919,6 @@ def break_anywhere(self, frame): return True return False - @skip_doctest def _is_in_decorator_internal_and_should_skip(self, frame): """ Utility to tell us whether we are in a decorator internal and should stop. diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 8a262525f34..0f765fec433 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -413,7 +413,6 @@ def pdb(self, parameter_s=''): self.shell.call_pdb = new_pdb print('Automatic pdb calling has been turned',on_off(new_pdb)) - @skip_doctest @magic_arguments.magic_arguments() @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE', help=""" diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index c0cb209daaa..0940df31c34 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -63,7 +63,6 @@ def __init__(self, shell=None, **kwargs): super().__init__(shell=shell, **kwargs) - @skip_doctest def _isexec_POSIX(self, file): """ Test for executable on a POSIX system @@ -75,14 +74,12 @@ def _isexec_POSIX(self, file): - @skip_doctest def _isexec_WIN(self, file): """ Test for executable file on non POSIX system """ return file.is_file() and self.execre.match(file.name) is not None - @skip_doctest def isexec(self, file): """ Test for executable file on non POSIX system @@ -633,8 +630,8 @@ def sc(self, parameter_s=''): # while the list form is useful to loop over: In [6]: for f in a.l: - ...: !wc -l $f - ...: + ...: !wc -l $f + ...: 146 setup.py 130 win32_manual_post_install.py From c70d54890b32de3b4f47dc80130c29a3359df1e9 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 19 Nov 2021 19:39:54 +0300 Subject: [PATCH 1733/3726] Remove old workaround for a bug fixed in Python 3.4 http://bugs.python.org/issue3158 --- IPython/core/completer.py | 3 ++ IPython/testing/plugin/ipdoctest.py | 78 ++--------------------------- 2 files changed, 6 insertions(+), 75 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index c6d20bbf5af..afb8a6ca2b2 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -132,6 +132,7 @@ from IPython.core.inputtransformer2 import ESC_MAGIC from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol from IPython.core.oinspect import InspectColors +from IPython.testing.skipdoctest import skip_doctest from IPython.utils import generics from IPython.utils.dir2 import dir2, get_real_method from IPython.utils.path import ensure_dir_exists @@ -190,6 +191,8 @@ class ProvisionalCompleterWarning(FutureWarning): warnings.filterwarnings('error', category=ProvisionalCompleterWarning) + +@skip_doctest @contextmanager def provisionalcompleter(action='ignore'): """ diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index d5c794b80ff..ad07d7d40fa 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -20,7 +20,6 @@ # From the standard library import doctest -import inspect import logging import os import re @@ -54,42 +53,8 @@ def __getattribute__(self,key): else: return getattr(object.__getattribute__(self,'obj'),key) -# Modified version of the one in the stdlib, that fixes a python bug (doctests -# not found in extension modules, http://bugs.python.org/issue3158) -class DocTestFinder(doctest.DocTestFinder): - - def _from_module(self, module, object): - """ - Return true if the given object is defined in the given - module. - """ - if module is None: - return True - elif inspect.isfunction(object): - return module.__dict__ is object.__globals__ - elif inspect.isbuiltin(object): - return module.__name__ == object.__module__ - elif inspect.isclass(object): - return module.__name__ == object.__module__ - elif inspect.ismethod(object): - # This one may be a bug in cython that fails to correctly set the - # __module__ attribute of methods, but since the same error is easy - # to make by extension code writers, having this safety in place - # isn't such a bad idea - return module.__name__ == object.__self__.__class__.__module__ - elif inspect.getmodule(object) is not None: - return module is inspect.getmodule(object) - elif hasattr(object, '__module__'): - return module.__name__ == object.__module__ - elif isinstance(object, property): - return True # [XX] no way not be sure. - elif inspect.ismethoddescriptor(object): - # Unbound PyQt signals reach this point in Python 3.4b3, and we want - # to avoid throwing an error. See also http://bugs.python.org/issue3158 - return False - else: - raise ValueError("object must be a class or function, got %r" % object) +class DocTestFinder(doctest.DocTestFinder): def _find(self, tests, obj, name, module, source_lines, globs, seen): """ Find tests for the given object and any contained objects, and @@ -99,45 +64,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): if bool(getattr(obj, "__skip_doctest__", False)): #print 'SKIPPING DOCTEST FOR:',obj # dbg obj = DocTestSkip(obj) - - doctest.DocTestFinder._find(self,tests, obj, name, module, - source_lines, globs, seen) - - # Below we re-run pieces of the above method with manual modifications, - # because the original code is buggy and fails to correctly identify - # doctests in extension modules. - - # Local shorthands - from inspect import isroutine, isclass - - # Look for tests in a module's contained objects. - if inspect.ismodule(obj) and self._recurse: - for valname, val in obj.__dict__.items(): - valname1 = '%s.%s' % (name, valname) - if ( (isroutine(val) or isclass(val)) - and self._from_module(module, val) ): - - self._find(tests, val, valname1, module, source_lines, - globs, seen) - - # Look for tests in a class's contained objects. - if inspect.isclass(obj) and self._recurse: - #print 'RECURSE into class:',obj # dbg - for valname, val in obj.__dict__.items(): - # Special handling for staticmethod/classmethod. - if isinstance(val, staticmethod): - val = getattr(obj, valname) - if isinstance(val, classmethod): - val = getattr(obj, valname).__func__ - - # Recurse to methods, properties, and nested classes. - if ((inspect.isfunction(val) or inspect.isclass(val) or - inspect.ismethod(val) or - isinstance(val, property)) and - self._from_module(module, val)): - valname = '%s.%s' % (name, valname) - self._find(tests, val, valname, module, source_lines, - globs, seen) + + super()._find(tests, obj, name, module, source_lines, globs, seen) class IPDoctestOutputChecker(doctest.OutputChecker): From 55086297a709ba23c44f8e393959a3d403cdbd55 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 19 Nov 2021 19:55:02 +0300 Subject: [PATCH 1734/3726] Refactor doctest-skipping implementation The old one rewrites docstring which creates a doctest where is no examples presented in the docstring. --- IPython/testing/plugin/ipdoctest.py | 33 ++++++----------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index ad07d7d40fa..dacb4d1d7c1 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -37,35 +37,16 @@ # Classes and functions #----------------------------------------------------------------------------- -class DocTestSkip(object): - """Object wrapper for doctests to be skipped.""" - - ds_skip = """Doctest to skip. - >>> 1 #doctest: +SKIP - """ - - def __init__(self,obj): - self.obj = obj - - def __getattribute__(self,key): - if key == '__doc__': - return DocTestSkip.ds_skip - else: - return getattr(object.__getattribute__(self,'obj'),key) - class DocTestFinder(doctest.DocTestFinder): - def _find(self, tests, obj, name, module, source_lines, globs, seen): - """ - Find tests for the given object and any contained objects, and - add them to `tests`. - """ - print('_find for:', obj, name, module) # dbg - if bool(getattr(obj, "__skip_doctest__", False)): - #print 'SKIPPING DOCTEST FOR:',obj # dbg - obj = DocTestSkip(obj) + def _get_test(self, obj, name, module, globs, source_lines): + test = super()._get_test(obj, name, module, globs, source_lines) + + if bool(getattr(obj, "__skip_doctest__", False)) and test is not None: + for example in test.examples: + example.options[doctest.SKIP] = True - super()._find(tests, obj, name, module, source_lines, globs, seen) + return test class IPDoctestOutputChecker(doctest.OutputChecker): From 4a68e2c977e87f5d62845db05ca798cbd6f2b786 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 16 Nov 2021 09:24:11 -0800 Subject: [PATCH 1735/3726] some typing --- IPython/core/interactiveshell.py | 28 ++++++++++++++++++++++------ mypy.ini | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 0dec7535356..de9691fb889 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -82,14 +82,17 @@ from logging import error import IPython.core.hooks -from typing import List as ListType, Tuple, Optional -from ast import AST +from typing import List as ListType, Tuple, Optional, Callable +from ast import AST, stmt + # NoOpContext is deprecated, but ipykernel imports it from here. # See https://github.com/ipython/ipykernel/issues/157 # (2016, let's try to remove than in IPython 8.0) from IPython.utils.contexts import NoOpContext +sphinxify: Optional[Callable] + try: import docrepr.sphinxify as sphx @@ -207,14 +210,20 @@ def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module: is updated only on `local()` calls. """ - from ast import Expr, Await, Return + from ast import Expr, Await, Return, stmt, FunctionDef, Try, AsyncFunctionDef if sys.version_info >= (3,8): return ast.parse(cell) tree = ast.parse(_asyncify(cell)) function_def = tree.body[0] + if sys.version_info > (3, 8): + assert isinstance(function_def, FunctionDef), function_def + else: + assert isinstance(function_def, (FunctionDef, AsyncFunctionDef)), function_def + function_def.name = wrapper_name try_block = function_def.body[0] + assert isinstance(try_block, Try) lastexpr = try_block.body[-1] if isinstance(lastexpr, (Expr, Await)): try_block.body[-1] = Return(lastexpr.value) @@ -312,7 +321,7 @@ class ExecutionResult(object): """ execution_count = None error_before_exec = None - error_in_exec = None + error_in_exec: Optional[BaseException] = None info = None result = None @@ -3278,8 +3287,14 @@ def transform_ast(self, node): ast.fix_missing_locations(node) return node - async def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr', - compiler=compile, result=None): + async def run_ast_nodes( + self, + nodelist: ListType[stmt], + cell_name: str, + interactivity="last_expr", + compiler=compile, + result=None, + ): """Run a sequence of AST nodes. The execution mode depends on the interactivity parameter. @@ -3318,6 +3333,7 @@ async def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivi if not nodelist: return + if interactivity == 'last_expr_or_assign': if isinstance(nodelist[-1], _assign_nodes): asg = nodelist[-1] diff --git a/mypy.ini b/mypy.ini index 998aed51a29..7c1be49cd62 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,4 @@ [mypy] -python_version = 3.6 +python_version = 3.8 ignore_missing_imports = True follow_imports = silent From 4a966fb07e2370c85dd66cb237f24a85e268e305 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 23 Nov 2021 22:57:56 +0300 Subject: [PATCH 1736/3726] Move most of Windows test runners to GitHub Actions Leave only 32-bit jobs on Appveyor, I don't know how to configure actions/setup-python to install 32-bit CPython. --- .github/workflows/test.yml | 5 +++-- appveyor.yml | 12 ------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9528f007ee..767f71db872 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest] python-version: ["3.7", "3.8", "3.9", "3.10"] deps: [test_extra] # Test all on ubuntu, test ends on macos @@ -49,11 +49,12 @@ jobs: python -m pip install --upgrade -e .[${{ matrix.deps }}] python -m pip install --upgrade check-manifest pytest-cov - name: Check manifest + if: runner.os != 'Windows' # setup.py does not support sdist on Windows run: check-manifest - name: pytest env: COLUMNS: 120 run: | - pytest --color=yes -v --cov --cov-report=xml + pytest --color=yes -ra -v --cov --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/appveyor.yml b/appveyor.yml index b524356f52e..4348ad05e04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,22 +9,10 @@ environment: matrix: - - PYTHON: "C:\\Python310-x64" - PYTHON_VERSION: "3.10.x" - PYTHON_ARCH: "64" - - - PYTHON: "C:\\Python37-x64" - PYTHON_VERSION: "3.7.x" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Python38" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "32" - - PYTHON: "C:\\Python38-x64" - PYTHON_VERSION: "3.8.x" - PYTHON_ARCH: "64" - init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" From 8e428a7175dabd6e0b624e7482b2e3a21679c048 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 23 Nov 2021 23:29:23 +0300 Subject: [PATCH 1737/3726] On Windows `Path.cwd()` might be in short filename/SFN format --- IPython/extensions/tests/test_storemagic.py | 3 ++- IPython/utils/tests/test_tempdir.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/IPython/extensions/tests/test_storemagic.py b/IPython/extensions/tests/test_storemagic.py index acf1abffdc2..3ac306bcddd 100644 --- a/IPython/extensions/tests/test_storemagic.py +++ b/IPython/extensions/tests/test_storemagic.py @@ -1,4 +1,5 @@ import tempfile, os +from pathlib import Path from traitlets.config.loader import Config @@ -43,7 +44,7 @@ def test_store_restore(): assert ip.user_ns["foobaz"] == "80" ip.magic("store -r") # restores _dh too - assert os.path.realpath(tmpd) in ip.user_ns["_dh"] + assert any(Path(tmpd).samefile(p) for p in ip.user_ns["_dh"]) os.rmdir(tmpd) diff --git a/IPython/utils/tests/test_tempdir.py b/IPython/utils/tests/test_tempdir.py index 68a3e036a75..9191b97acb8 100644 --- a/IPython/utils/tests/test_tempdir.py +++ b/IPython/utils/tests/test_tempdir.py @@ -22,8 +22,8 @@ def test_named_file_in_temporary_directory(): def test_temporary_working_directory(): with TemporaryWorkingDirectory() as directory: - directory_path = Path(directory) + directory_path = Path(directory).resolve() assert directory_path.exists() - assert Path.cwd() == directory_path.resolve() + assert Path.cwd().resolve() == directory_path assert not directory_path.exists() - assert Path.cwd() != directory_path.resolve() + assert Path.cwd().resolve() != directory_path From 59cf48e40e1bafdb27bea54695e5bc5c6dd81f81 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 24 Nov 2021 00:38:12 +0300 Subject: [PATCH 1738/3726] TST: Simplify `NamedInstanceClass` The `__getitem__`/`get_instance` methods are unused. --- IPython/core/tests/test_completer.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index c470b1fe723..4feba09a651 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -191,25 +191,16 @@ def test_line_split(): check_line_split(sp, [map(str, p) for p in t]) -class NamedInstanceMetaclass(type): - def __getitem__(cls, item): - return cls.get_instance(item) +class NamedInstanceClass: + instances = {} - -class NamedInstanceClass(metaclass=NamedInstanceMetaclass): def __init__(self, name): - if not hasattr(self.__class__, "instances"): - self.__class__.instances = {} - self.__class__.instances[name] = self + self.instances[name] = self @classmethod def _ipython_key_completions_(cls): return cls.instances.keys() - @classmethod - def get_instance(cls, name): - return cls.instances[name] - class KeyCompletable: def __init__(self, things=()): From 3070118fe87ac4f89c49c2afe45503c22ef73e70 Mon Sep 17 00:00:00 2001 From: Gal B Date: Tue, 23 Nov 2021 22:16:28 +0200 Subject: [PATCH 1739/3726] Remove version check for globals --- IPython/core/interactiveshell.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 0dec7535356..ce4ac836357 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -117,12 +117,8 @@ class ProvisionalWarning(DeprecationWarning): from ast import Module as OriginalModule Module = lambda nodelist, type_ignores: OriginalModule(nodelist) -if sys.version_info > (3,6): - _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) - _single_targets_nodes = (ast.AugAssign, ast.AnnAssign) -else: - _assign_nodes = (ast.AugAssign, ast.Assign ) - _single_targets_nodes = (ast.AugAssign, ) +_assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) +_single_targets_nodes = (ast.AugAssign, ast.AnnAssign) #----------------------------------------------------------------------------- # Await Helpers From b6d84ef95e42f96cf48bb8d4e8f150c8eb37a8eb Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 24 Nov 2021 00:51:11 +0300 Subject: [PATCH 1740/3726] `io.StringIO` always has `encoding` field At lease since Python 2.7/3.5+ --- IPython/core/tests/test_magic_terminal.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/IPython/core/tests/test_magic_terminal.py b/IPython/core/tests/test_magic_terminal.py index cffb7674c8e..d7984280d75 100644 --- a/IPython/core/tests/test_magic_terminal.py +++ b/IPython/core/tests/test_magic_terminal.py @@ -21,9 +21,6 @@ def check_cpaste(code, should_fail=False): ip.user_ns['code_ran'] = False src = StringIO() - if not hasattr(src, 'encoding'): - # IPython expects stdin to have an encoding attribute - src.encoding = None src.write(code) src.write('\n--\n') src.seek(0) From 5b9aa9c88325012439384b5a704e2b3d183c3366 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 23 Nov 2021 11:33:35 -0800 Subject: [PATCH 1741/3726] Stop support for 3.7 on the master branch. According to NEP 29, Python 3.7 support can be stopped this december. https://numpy.org/neps/nep-0029-deprecation_policy.html Here we start dropping support for 3.7 as we'll release 8.0 likely early 2022 --- .github/workflows/test.yml | 4 ++-- IPython/__init__.py | 7 ++++--- appveyor.yml | 1 - setup.py | 23 ++++++++++++----------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 767f71db872..2620162c209 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,12 +15,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] deps: [test_extra] # Test all on ubuntu, test ends on macos include: - os: macos-latest - python-version: "3.7" + python-version: "3.8" deps: test_extra - os: macos-latest python-version: "3.10" diff --git a/IPython/__init__.py b/IPython/__init__.py index 55cb5ef61bd..5df72f83775 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """ IPython: tools for interactive and parallel computing in Python. @@ -27,13 +26,15 @@ #----------------------------------------------------------------------------- # Don't forget to also update setup.py when this changes! -if sys.version_info < (3, 6): +if sys.version_info < (3, 8): raise ImportError( """ -IPython 7.10+ supports Python 3.6 and above. +IPython 8+ supports Python 3.8 and above, following NEP 29. When using Python 2.7, please install IPython 5.x LTS Long Term Support version. Python 3.3 and 3.4 were supported up to IPython 6.x. Python 3.5 was supported with IPython 7.0 to 7.9. +Python 3.6 was supported with IPython up to 7.16. +Python 3.7 was still supported with the 7.x branch. See IPython `README.rst` file for more information: diff --git a/appveyor.yml b/appveyor.yml index 4348ad05e04..fbd8a2604f2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,6 @@ environment: COLUMNS: 120 # Appveyor web viwer window width is 130 chars matrix: - - PYTHON: "C:\\Python38" PYTHON_VERSION: "3.8.x" PYTHON_ARCH: "32" diff --git a/setup.py b/setup.py index bc536a84d77..69279cdf438 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ # # This check is also made in IPython/__init__, don't forget to update both when # changing Python version requirements. -if sys.version_info < (3, 7): +if sys.version_info < (3, 8): pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' try: import pip @@ -41,11 +41,12 @@ error = """ -IPython 7.17+ supports Python 3.7 and above, following NEP 29. +IPython 8+ supports Python 3.8 and above, following NEP 29. When using Python 2.7, please install IPython 5.x LTS Long Term Support version. Python 3.3 and 3.4 were supported up to IPython 6.x. Python 3.5 was supported with IPython 7.0 to 7.9. Python 3.6 was supported with IPython up to 7.16. +Python 3.7 was still supported with the 7.x branch. See IPython `README.rst` file for more information: @@ -244,15 +245,15 @@ everything.update(deps) extras_require['all'] = list(sorted(everything)) -if 'setuptools' in sys.modules: - setuptools_extra_args['python_requires'] = '>=3.7' - setuptools_extra_args['zip_safe'] = False - setuptools_extra_args['entry_points'] = { - 'console_scripts': find_entry_points(), - 'pygments.lexers': [ - 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer', - 'ipython = IPython.lib.lexers:IPythonLexer', - 'ipython3 = IPython.lib.lexers:IPython3Lexer', +if "setuptools" in sys.modules: + setuptools_extra_args["python_requires"] = ">=3.8" + setuptools_extra_args["zip_safe"] = False + setuptools_extra_args["entry_points"] = { + "console_scripts": find_entry_points(), + "pygments.lexers": [ + "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer", + "ipython = IPython.lib.lexers:IPythonLexer", + "ipython3 = IPython.lib.lexers:IPython3Lexer", ], } setup_args['extras_require'] = extras_require From 16e7375c5b7260504e5db805150119ff192f57f8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 6 Nov 2021 19:36:32 -0700 Subject: [PATCH 1742/3726] A few modification to fix docs passing. Also make sure some of the function signature are reproducible by not having the hex address of objects in reprs. --- IPython/core/completer.py | 8 +++++++- IPython/core/interactiveshell.py | 4 ++-- IPython/core/pylabtools.py | 6 +++--- IPython/utils/text.py | 3 +++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index afb8a6ca2b2..356a1e56568 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -177,7 +177,13 @@ # may have trouble processing. MATCHES_LIMIT = 500 -_deprecation_readline_sentinel = object() + +class Sentinel: + def __repr__(self): + return "" + + +_deprecation_readline_sentinel = Sentinel() class ProvisionalCompleterWarning(FutureWarning): diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1838bf3e698..2bd8cea6522 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -3004,7 +3004,7 @@ def should_run_async( result: bool Whether the code needs to be run with a coroutine runner or not - .. versionadded: 7.0 + .. versionadded:: 7.0 """ if not self.autoawait: return False @@ -3069,7 +3069,7 @@ async def run_cell_async( ------- result : :class:`ExecutionResult` - .. versionadded: 7.0 + .. versionadded:: 7.0 """ info = ExecutionInfo( raw_cell, store_history, silent, shell_futures) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 36b7276b8b2..711ee758e77 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -119,7 +119,7 @@ def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs): If `base64` is True, return base64-encoded str instead of raw bytes for binary-encoded image formats - .. versionadded: 7.29 + .. versionadded:: 7.29 base64 argument """ # When there's an empty figure, we shouldn't return anything, otherwise we @@ -162,7 +162,7 @@ def retina_figure(fig, base64=False, **kwargs): If `base64` is True, return base64-encoded str instead of raw bytes for binary-encoded image formats - .. versionadded: 7.29 + .. versionadded:: 7.29 base64 argument """ pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs) @@ -399,7 +399,7 @@ def import_pylab(user_ns, import_all=True): def configure_inline_support(shell, backend): """ - .. deprecated: 7.23 + .. deprecated:: 7.23 use `matplotlib_inline.backend_inline.configure_inline_support()` diff --git a/IPython/utils/text.py b/IPython/utils/text.py index c25869ab397..a1754d254de 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -593,6 +593,9 @@ def parse(self, fmt_string): # Re-yield the {foo} style pattern yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion) + def __repr__(self): + return "" + #----------------------------------------------------------------------------- # Utils to columnize a list of string #----------------------------------------------------------------------------- From 2591ecb9fda06ead972f42fcbe71d5db2285ddd8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 3 Nov 2021 08:06:53 -0700 Subject: [PATCH 1743/3726] Fix debugger history outside of IPython. This fixes the debugger when ran outside of IPython context as self.shell.debugger_history is not set. --- IPython/terminal/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index 1c4fb6ab413..d76550d878f 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -73,7 +73,7 @@ def gen_comp(self, text): message=(lambda: PygmentsTokens(get_prompt_tokens())), editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), key_bindings=create_ipython_shortcuts(self.shell), - history=self.shell.debugger_history, + history=self.debugger_history, completer=self._ptcomp, enable_history_search=True, mouse_support=self.shell.mouse_support, From c079d3827dcec3f2ffa23885f5c804528548d34e Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 24 Nov 2021 17:39:55 +0300 Subject: [PATCH 1744/3726] Don't build docs for deprecated `utils.version` Reuse `utils.warn` entry as it is no longer presented. --- docs/autogen_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/autogen_api.py b/docs/autogen_api.py index 46a806ca95f..6abda9c93ca 100755 --- a/docs/autogen_api.py +++ b/docs/autogen_api.py @@ -55,7 +55,7 @@ r'\.parallel', r'\.qt', # this is deprecated. - r'\.utils\.warn', + r'\.utils\.version', # Private APIs (there should be a lot more here) r'\.terminal\.ptutils', ] From 919f313f6ff7c43dbcf7d63b26de5e93351c4f9c Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 24 Nov 2021 18:11:24 +0300 Subject: [PATCH 1745/3726] darker --- docs/autogen_api.py | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/docs/autogen_api.py b/docs/autogen_api.py index 6abda9c93ca..d2fdfaeb046 100755 --- a/docs/autogen_api.py +++ b/docs/autogen_api.py @@ -35,30 +35,31 @@ # The inputhook* modules often cause problems on import, such as trying to # load incompatible Qt bindings. It's easiest to leave them all out. The - docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+', - r'\.ipdoctest', - r'\.testing\.plugin', - # Backwards compat import for lib.lexers - r'\.nbconvert\.utils\.lexers', - # We document this manually. - r'\.utils\.py3compat', - # These are exposed in display - r'\.core\.display', - r'\.lib\.display', - # Shims - r'\.config', - r'\.consoleapp', - r'\.frontend$', - r'\.html', - r'\.nbconvert', - r'\.nbformat', - r'\.parallel', - r'\.qt', - # this is deprecated. - r'\.utils\.version', - # Private APIs (there should be a lot more here) - r'\.terminal\.ptutils', - ] + docwriter.module_skip_patterns += [ + r"\.lib\.inputhook.+", + r"\.ipdoctest", + r"\.testing\.plugin", + # Backwards compat import for lib.lexers + r"\.nbconvert\.utils\.lexers", + # We document this manually. + r"\.utils\.py3compat", + # These are exposed in display + r"\.core\.display", + r"\.lib\.display", + # Shims + r"\.config", + r"\.consoleapp", + r"\.frontend$", + r"\.html", + r"\.nbconvert", + r"\.nbformat", + r"\.parallel", + r"\.qt", + # this is deprecated. + r"\.utils\.version", + # Private APIs (there should be a lot more here) + r"\.terminal\.ptutils", + ] # main API is in the inputhook module, which is documented. # These modules import functions and classes from other places to expose From fd4b9610bf1314346be8b51e72129d8635e6a7f5 Mon Sep 17 00:00:00 2001 From: Gal B Date: Thu, 25 Nov 2021 00:43:38 +0200 Subject: [PATCH 1746/3726] Remove version check for import --- IPython/core/interactiveshell.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1838bf3e698..069585b9068 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -112,13 +112,7 @@ class ProvisionalWarning(DeprecationWarning): """ pass -if sys.version_info > (3,8): - from ast import Module -else : - # mock the new API, ignore second argument - # see https://github.com/ipython/ipython/issues/11590 - from ast import Module as OriginalModule - Module = lambda nodelist, type_ignores: OriginalModule(nodelist) +from ast import Module _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) _single_targets_nodes = (ast.AugAssign, ast.AnnAssign) From 0ec26e4a01a9fea4f59a438bc4f8f890c1efafe4 Mon Sep 17 00:00:00 2001 From: Gal B Date: Thu, 25 Nov 2021 00:44:38 +0200 Subject: [PATCH 1747/3726] Remove no-op function in interactiveshell.py --- IPython/core/interactiveshell.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 069585b9068..3fa92663218 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -690,7 +690,6 @@ def __init__(self, ipython_dir=None, profile_dir=None, self.init_pdb() self.init_extension_manager() self.init_payload() - self.init_deprecation_warnings() self.hooks.late_startup_hook() self.events.trigger('shell_initialized', self) atexit.register(self.atexit_operations) @@ -816,16 +815,6 @@ def init_logstart(self): elif self.logstart: self.magic('logstart') - def init_deprecation_warnings(self): - """ - register default filter for deprecation warning. - - This will allow deprecation warning of function used interactively to show - warning to users, and still hide deprecation warning from libraries import. - """ - if sys.version_info < (3,7): - warnings.filterwarnings("default", category=DeprecationWarning, module=self.user_ns.get("__name__")) - def init_builtins(self): # A single, static flag that we set to True. Its presence indicates From f6d27b5d004bfb844a2ad43b61fee6ad8cfc8da3 Mon Sep 17 00:00:00 2001 From: Gal B Date: Thu, 25 Nov 2021 00:45:28 +0200 Subject: [PATCH 1748/3726] Remove unused ast_asyncify() and related code The code removed here in unreachable after moving to Python >=3.8 as shown in: https://app.codecov.io/gh/ipython/ipython/compare/13315/changes --- IPython/core/interactiveshell.py | 210 ++++--------------------------- 1 file changed, 25 insertions(+), 185 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 3fa92663218..6f8656478af 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -83,7 +83,7 @@ import IPython.core.hooks from typing import List as ListType, Tuple, Optional, Callable -from ast import AST, stmt +from ast import stmt # NoOpContext is deprecated, but ipykernel imports it from here. @@ -121,104 +121,11 @@ class ProvisionalWarning(DeprecationWarning): # Await Helpers #----------------------------------------------------------------------------- -def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: - """Return a function that do not create a new local scope. - - Given a function, create a clone of this function where the co_newlocal flag - has been removed, making this function code actually run in the sourounding - scope. - - We need this in order to run asynchronous code in user level namespace. - """ - from types import CodeType, FunctionType - CO_NEWLOCALS = 0x0002 - code = function.__code__ - new_co_flags = code.co_flags & ~CO_NEWLOCALS - if sys.version_info > (3, 8, 0, 'alpha', 3): - new_code = code.replace(co_flags=new_co_flags) - else: - new_code = CodeType( - code.co_argcount, - code.co_kwonlyargcount, - code.co_nlocals, - code.co_stacksize, - new_co_flags, - code.co_code, - code.co_consts, - code.co_names, - code.co_varnames, - code.co_filename, - code.co_name, - code.co_firstlineno, - code.co_lnotab, - code.co_freevars, - code.co_cellvars - ) - return FunctionType(new_code, globals(), function.__name__, function.__defaults__) - - # we still need to run things using the asyncio eventloop, but there is no # async integration -from .async_helpers import (_asyncio_runner, _asyncify, _pseudo_sync_runner) +from .async_helpers import (_asyncio_runner, _pseudo_sync_runner) from .async_helpers import _curio_runner, _trio_runner, _should_be_async - -def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module: - """ - Parse a cell with top-level await and modify the AST to be able to run it later. - - Parameters - ---------- - cell: str - The code cell to asyncronify - wrapper_name: str - The name of the function to be used to wrap the passed `cell`. It is - advised to **not** use a python identifier in order to not pollute the - global namespace in which the function will be ran. - - Returns - ------- - ModuleType: - A module object AST containing **one** function named `wrapper_name`. - - The given code is wrapped in a async-def function, parsed into an AST, and - the resulting function definition AST is modified to return the last - expression. - - The last expression or await node is moved into a return statement at the - end of the function, and removed from its original location. If the last - node is not Expr or Await nothing is done. - - The function `__code__` will need to be later modified (by - ``removed_co_newlocals``) in a subsequent step to not create new `locals()` - meaning that the local and global scope are the same, ie as if the body of - the function was at module level. - - Lastly a call to `locals()` is made just before the last expression of the - function, or just after the last assignment or statement to make sure the - global dict is updated as python function work with a local fast cache which - is updated only on `local()` calls. - """ - - from ast import Expr, Await, Return, stmt, FunctionDef, Try, AsyncFunctionDef - if sys.version_info >= (3,8): - return ast.parse(cell) - tree = ast.parse(_asyncify(cell)) - - function_def = tree.body[0] - if sys.version_info > (3, 8): - assert isinstance(function_def, FunctionDef), function_def - else: - assert isinstance(function_def, (FunctionDef, AsyncFunctionDef)), function_def - - function_def.name = wrapper_name - try_block = function_def.body[0] - assert isinstance(try_block, Try) - lastexpr = try_block.body[-1] - if isinstance(lastexpr, (Expr, Await)): - try_block.body[-1] = Return(lastexpr.value) - ast.fix_missing_locations(tree) - return tree #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -3136,28 +3043,7 @@ def error_before_exec(value): with self.display_trap: # Compile to bytecode try: - if sys.version_info < (3,8) and self.autoawait: - if _should_be_async(cell): - # the code AST below will not be user code: we wrap it - # in an `async def`. This will likely make some AST - # transformer below miss some transform opportunity and - # introduce a small coupling to run_code (in which we - # bake some assumptions of what _ast_asyncify returns. - # they are ways around (like grafting part of the ast - # later: - # - Here, return code_ast.body[0].body[1:-1], as well - # as last expression in return statement which is - # the user code part. - # - Let it go through the AST transformers, and graft - # - it back after the AST transform - # But that seem unreasonable, at least while we - # do not need it. - code_ast = _ast_asyncify(cell, 'async-def-wrapper') - _run_async = True - else: - code_ast = compiler.ast_parse(cell, filename=cell_name) - else: - code_ast = compiler.ast_parse(cell, filename=cell_name) + code_ast = compiler.ast_parse(cell, filename=cell_name) except self.custom_exceptions as e: etype, value, tb = sys.exc_info() self.CustomTB(etype, value, tb) @@ -3183,8 +3069,6 @@ def error_before_exec(value): # Execute the user code interactivity = "none" if silent else self.ast_node_interactivity - if _run_async: - interactivity = 'async' has_raised = await self.run_ast_nodes(code_ast.body, cell_name, interactivity=interactivity, compiler=compiler, result=result) @@ -3293,11 +3177,6 @@ async def run_ast_nodes( or the last assignment. Other values for this parameter will raise a ValueError. - Experimental value: 'async' Will try to run top level interactive - async/await code in default runner, this will not respect the - interactivity setting and will only run the last node if it is an - expression. - compiler : callable A function with the same interface as the built-in compile(), to turn the AST nodes into code objects. Default is the built-in compile(). @@ -3341,52 +3220,32 @@ async def run_ast_nodes( to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:] elif interactivity == 'all': to_run_exec, to_run_interactive = [], nodelist - elif interactivity == 'async': - to_run_exec, to_run_interactive = [], nodelist - _async = True else: raise ValueError("Interactivity was %r" % interactivity) try: - if _async and sys.version_info > (3,8): - raise ValueError("This branch should never happen on Python 3.8 and above, " - "please try to upgrade IPython and open a bug report with your case.") - if _async: - # If interactivity is async the semantics of run_code are - # completely different Skip usual machinery. - mod = Module(nodelist, []) - async_wrapper_code = compiler(mod, cell_name, 'exec') - exec(async_wrapper_code, self.user_global_ns, self.user_ns) - async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__ - if (await self.run_code(async_code, result, async_=True)): + def compare(code): + is_async = (inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE) + return is_async + + # refactor that to just change the mod constructor. + to_run = [] + for node in to_run_exec: + to_run.append((node, 'exec')) + + for node in to_run_interactive: + to_run.append((node, 'single')) + + for node,mode in to_run: + if mode == 'exec': + mod = Module([node], []) + elif mode == 'single': + mod = ast.Interactive([node]) + with compiler.extra_flags(getattr(ast, 'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0) if self.autoawait else 0x0): + code = compiler(mod, cell_name, mode) + asy = compare(code) + if (await self.run_code(code, result, async_=asy)): return True - else: - if sys.version_info > (3, 8): - def compare(code): - is_async = (inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE) - return is_async - else: - def compare(code): - return _async - - # refactor that to just change the mod constructor. - to_run = [] - for node in to_run_exec: - to_run.append((node, 'exec')) - - for node in to_run_interactive: - to_run.append((node, 'single')) - - for node,mode in to_run: - if mode == 'exec': - mod = Module([node], []) - elif mode == 'single': - mod = ast.Interactive([node]) - with compiler.extra_flags(getattr(ast, 'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0) if self.autoawait else 0x0): - code = compiler(mod, cell_name, mode) - asy = compare(code) - if (await self.run_code(code, result, async_=asy)): - return True # Flush softspace if softspace(sys.stdout, 0): @@ -3409,21 +3268,6 @@ def compare(code): return False - def _async_exec(self, code_obj: types.CodeType, user_ns: dict): - """ - Evaluate an asynchronous code object using a code runner - - Fake asynchronous execution of code_object in a namespace via a proxy namespace. - - Returns coroutine object, which can be executed via async loop runner - - WARNING: The semantics of `async_exec` are quite different from `exec`, - in particular you can only pass a single namespace. It also return a - handle to the value of the last things returned by code_object. - """ - - return eval(code_obj, user_ns) - async def run_code(self, code_obj, result=None, *, async_=False): """Execute a code object. @@ -3458,11 +3302,7 @@ async def run_code(self, code_obj, result=None, *, async_=False): try: try: self.hooks.pre_run_code_hook() - if async_ and sys.version_info < (3,8): - last_expr = (await self._async_exec(code_obj, self.user_ns)) - code = compile('last_expr', 'fake', "single") - exec(code, {'last_expr': last_expr}) - elif async_ : + if async_ : await eval(code_obj, self.user_global_ns, self.user_ns) else: exec(code_obj, self.user_global_ns, self.user_ns) From c8ed4d8189d813b322516bc352eab2768e75ec5b Mon Sep 17 00:00:00 2001 From: Gal B Date: Thu, 25 Nov 2021 00:54:50 +0200 Subject: [PATCH 1749/3726] Apply linter on interactiveshell.py --- IPython/core/interactiveshell.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 6f8656478af..34cdaaba8d0 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -123,7 +123,7 @@ class ProvisionalWarning(DeprecationWarning): # we still need to run things using the asyncio eventloop, but there is no # async integration -from .async_helpers import (_asyncio_runner, _pseudo_sync_runner) +from .async_helpers import _asyncio_runner, _pseudo_sync_runner from .async_helpers import _curio_runner, _trio_runner, _should_be_async #----------------------------------------------------------------------------- @@ -3224,27 +3224,32 @@ async def run_ast_nodes( raise ValueError("Interactivity was %r" % interactivity) try: + def compare(code): - is_async = (inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE) + is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE return is_async # refactor that to just change the mod constructor. to_run = [] for node in to_run_exec: - to_run.append((node, 'exec')) + to_run.append((node, "exec")) for node in to_run_interactive: - to_run.append((node, 'single')) + to_run.append((node, "single")) - for node,mode in to_run: - if mode == 'exec': + for node, mode in to_run: + if mode == "exec": mod = Module([node], []) - elif mode == 'single': + elif mode == "single": mod = ast.Interactive([node]) - with compiler.extra_flags(getattr(ast, 'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0) if self.autoawait else 0x0): + with compiler.extra_flags( + getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + if self.autoawait + else 0x0 + ): code = compiler(mod, cell_name, mode) asy = compare(code) - if (await self.run_code(code, result, async_=asy)): + if await self.run_code(code, result, async_=asy): return True # Flush softspace @@ -3302,7 +3307,7 @@ async def run_code(self, code_obj, result=None, *, async_=False): try: try: self.hooks.pre_run_code_hook() - if async_ : + if async_: await eval(code_obj, self.user_global_ns, self.user_ns) else: exec(code_obj, self.user_global_ns, self.user_ns) From cb2e14e314e437c8f46ba18ee478d673feffefe6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 25 Nov 2021 17:33:22 -0800 Subject: [PATCH 1750/3726] Remove pre-3.8 code path --- IPython/core/async_helpers.py | 94 +++-------------------------------- 1 file changed, 7 insertions(+), 87 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 9d2e5ddaab0..72220511838 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -12,13 +12,10 @@ import ast -import sys import inspect -from textwrap import dedent, indent class _AsyncIORunner: - def __call__(self, coro): """ Handler for asyncio autoawait @@ -28,7 +25,8 @@ def __call__(self, coro): return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro) def __str__(self): - return 'asyncio' + return "asyncio" + _asyncio_runner = _AsyncIORunner() @@ -74,69 +72,6 @@ def _pseudo_sync_runner(coro): ) -def _asyncify(code: str) -> str: - """wrap code in async def definition. - - And setup a bit of context to run it later. - """ - res = dedent( - """ - async def __wrapper__(): - try: - {usercode} - finally: - locals() - """ - ).format(usercode=indent(code, " " * 8)) - return res - - -class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): - """ - Find syntax errors that would be an error in an async repl, but because - the implementation involves wrapping the repl in an async function, it - is erroneously allowed (e.g. yield or return at the top level) - """ - def __init__(self): - if sys.version_info >= (3,8): - raise ValueError('DEPRECATED in Python 3.8+') - self.depth = 0 - super().__init__() - - def generic_visit(self, node): - func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - invalid_types_by_depth = { - 0: (ast.Return, ast.Yield, ast.YieldFrom), - 1: (ast.Nonlocal,) - } - - should_traverse = self.depth < max(invalid_types_by_depth.keys()) - if isinstance(node, func_types) and should_traverse: - self.depth += 1 - super().generic_visit(node) - self.depth -= 1 - elif isinstance(node, invalid_types_by_depth[self.depth]): - raise SyntaxError() - else: - super().generic_visit(node) - - -def _async_parse_cell(cell: str) -> ast.AST: - """ - This is a compatibility shim for pre-3.7 when async outside of a function - is a syntax error at the parse stage. - - It will return an abstract syntax tree parsed as if async and await outside - of a function were not a syntax error. - """ - if sys.version_info < (3, 7): - # Prior to 3.7 you need to asyncify before parse - wrapped_parse_tree = ast.parse(_asyncify(cell)) - return wrapped_parse_tree.body[0].body[0] - else: - return ast.parse(cell) - - def _should_be_async(cell: str) -> bool: """Detect if a block of code need to be wrapped in an `async def` @@ -148,25 +83,10 @@ def _should_be_async(cell: str) -> bool: Not handled yet: If the block of code has a return statement as the top level, it will be seen as async. This is a know limitation. """ - if sys.version_info > (3, 8): - try: - code = compile(cell, "<>", "exec", flags=getattr(ast,'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0)) - return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE - except (SyntaxError, MemoryError): - return False try: - # we can't limit ourself to ast.parse, as it __accepts__ to parse on - # 3.7+, but just does not _compile_ - code = compile(cell, "<>", "exec") + code = compile( + cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + ) + return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE except (SyntaxError, MemoryError): - try: - parse_tree = _async_parse_cell(cell) - - # Raise a SyntaxError if there are top-level return or yields - v = _AsyncSyntaxErrorVisitor() - v.visit(parse_tree) - - except (SyntaxError, MemoryError): - return False - return True - return False + return False From 92cba8c3ace2c279dbfadcba3b6f9bac565b8825 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 25 Nov 2021 17:37:36 -0800 Subject: [PATCH 1751/3726] More cleanup of pre-3.8 codepath --- IPython/core/inputtransformer2.py | 5 +--- IPython/core/magics/execution.py | 47 ++++++++++++++----------------- IPython/core/tests/test_magic.py | 7 +---- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 53e114c7139..ee7015924dd 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -791,9 +791,6 @@ def __init__(self, extra_flags=0): self.compiler = MaybeAsyncCompile(extra_flags=extra_flags) -if (sys.version_info.major, sys.version_info.minor) >= (3, 8): - _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT -else: - _extra_flags = ast.PyCF_ONLY_AST +_extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 0f765fec433..d74f4c2de2f 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -8,49 +8,44 @@ import ast import bdb import builtins as builtin_mod +import cProfile as profile import gc import itertools +import math import os +import pstats +import re import shlex import sys import time import timeit -import math -import re +from ast import Module +from io import StringIO +from logging import error +from pathlib import Path from pdb import Restart +from warnings import warn -import cProfile as profile -import pstats - -from IPython.core import oinspect -from IPython.core import magic_arguments -from IPython.core import page +from IPython.core import magic_arguments, oinspect, page from IPython.core.error import UsageError from IPython.core.macro import Macro -from IPython.core.magic import (Magics, magics_class, line_magic, cell_magic, - line_cell_magic, on_off, needs_local_scope, - no_var_expand) +from IPython.core.magic import ( + Magics, + cell_magic, + line_cell_magic, + line_magic, + magics_class, + needs_local_scope, + no_var_expand, + on_off, +) from IPython.testing.skipdoctest import skip_doctest -from IPython.utils.contexts import preserve_keys from IPython.utils.capture import capture_output +from IPython.utils.contexts import preserve_keys from IPython.utils.ipstruct import Struct from IPython.utils.module_paths import find_mod from IPython.utils.path import get_py_filename, shellglob from IPython.utils.timing import clock, clock2 -from warnings import warn -from logging import error -from pathlib import Path -from io import StringIO -from pathlib import Path - -if sys.version_info > (3,8): - from ast import Module -else : - # mock the new API, ignore second argument - # see https://github.com/ipython/ipython/issues/11590 - from ast import Module as OriginalModule - Module = lambda nodelist, type_ignores: OriginalModule(nodelist) - #----------------------------------------------------------------------------- # Magic implementation classes diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index a72eb68cd7a..fdd6d90445f 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1308,12 +1308,7 @@ def test_time_no_var_expand(): # this is slow, put at the end for local testing. def test_timeit_arguments(): "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" - if sys.version_info < (3,7): - _ip.magic("timeit -n1 -r1 ('#')") - else: - # 3.7 optimize no-op statement like above out, and complain there is - # nothing in the for loop. - _ip.magic("timeit -n1 -r1 a=('#')") + _ip.magic("timeit -n1 -r1 a=('#')") TEST_MODULE = """ From b835cc1d5d3c11e59f3d22af1d054bec42d4e92d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 25 Nov 2021 17:53:58 -0800 Subject: [PATCH 1752/3726] remove last bits of pre-38 codepath. Closes #13316 --- IPython/terminal/shortcuts.py | 7 +++---- IPython/testing/decorators.py | 5 ----- IPython/testing/tools.py | 3 --- IPython/utils/tests/test_path.py | 12 +++++++----- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index 26e1883fd61..029adc9faad 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -163,10 +163,9 @@ def _(event): kb.add(*keys, filter=focused_insert & ebivim)(cmd) def get_input_mode(self): - if sys.version_info[0] == 3: - app = get_app() - app.ttimeoutlen = shell.ttimeoutlen - app.timeoutlen = shell.timeoutlen + app = get_app() + app.ttimeoutlen = shell.ttimeoutlen + app.timeoutlen = shell.timeoutlen return self._input_mode diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index a797c1c428e..d2f325ba3ca 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -162,11 +162,6 @@ def module_not_available(module): skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg) - -# Decorators to skip certain tests on specific platform/python combinations -skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt') - - # Other skip decorators # generic skip without module diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 3dec1ffc634..31bfd426863 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -191,9 +191,6 @@ def ipexec(fname, options=None, commands=()): # Absolute path for filename full_fname = os.path.join(test_dir, fname) full_cmd = ipython_cmd + cmdargs + ['--', full_fname] - if sys.platform == "win32" and sys.version_info < (3, 8): - # subprocess.Popen does not support Path objects yet - full_cmd = list(map(str, full_cmd)) env = os.environ.copy() # FIXME: ignore all warnings in ipexec while we have shims # should we keep suppressing warnings here, even after removing shims? diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index 3faf91dc646..6f291502826 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -19,9 +19,11 @@ import IPython from IPython import paths from IPython.testing import decorators as dec -from IPython.testing.decorators import (skip_if_not_win32, skip_win32, - onlyif_unicode_paths, - skip_win32_py38,) +from IPython.testing.decorators import ( + skip_if_not_win32, + skip_win32, + onlyif_unicode_paths, +) from IPython.testing.tools import make_tempfile from IPython.utils import path from IPython.utils.tempdir import TemporaryDirectory @@ -138,7 +140,7 @@ def test_get_home_dir_2(): assert home_dir == unfrozen -@skip_win32_py38 +@skip_win32 @with_environment def test_get_home_dir_3(): """get_home_dir() uses $HOME if set""" @@ -156,7 +158,7 @@ def test_get_home_dir_4(): # this should still succeed, but we don't care what the answer is home = path.get_home_dir(False) -@skip_win32_py38 +@skip_win32 @with_environment def test_get_home_dir_5(): """raise HomeDirError if $HOME is specified, but not a writable dir""" From 3494468c60a760c0c71d7b22c99b8443b84560be Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 26 Nov 2021 18:37:36 +0300 Subject: [PATCH 1753/3726] Remove unneeded workaround The globals update stuff is done in the plugin. --- IPython/testing/plugin/ipdoctest.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index dacb4d1d7c1..b593336b5cc 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -328,18 +328,6 @@ class IPDocTestRunner(doctest.DocTestRunner,object): """ def run(self, test, compileflags=None, out=None, clear_globs=True): - - # Hack: ipython needs access to the execution context of the example, - # so that it can propagate user variables loaded by %run into - # test.globs. We put them here into our modified %run as a function - # attribute. Our new %run will then only make the namespace update - # when called (rather than unconditionally updating test.globs here - # for all examples, most of which won't be calling %run anyway). - #_ip._ipdoctest_test_globs = test.globs - #_ip._ipdoctest_test_filename = test.filename - - test.globs.update(_ip.user_ns) - # Override terminal size to standardise traceback format with modified_env({'COLUMNS': '80', 'LINES': '24'}): return super(IPDocTestRunner,self).run(test, From 140d38350ca0e1bb92245dfecf9e2048e7821605 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 26 Nov 2021 18:38:59 +0300 Subject: [PATCH 1754/3726] Remove unimplemented `ipdoctest: external` support --- IPython/testing/plugin/ipdoctest.py | 40 +++-------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index b593336b5cc..52cd8fd3b8a 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -80,18 +80,6 @@ def check_output(self, want, got, optionflags): class IPExample(doctest.Example): pass -class IPExternalExample(doctest.Example): - """Doctest examples to be run in an external process.""" - - def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, - options=None): - # Parent constructor - doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) - - # An EXTRA newline is needed to prevent pexpect hangs - self.source += '\n' - - class IPDocTestParser(doctest.DocTestParser): """ A class used to parse strings containing doctest examples. @@ -137,9 +125,6 @@ class IPDocTestParser(doctest.DocTestParser): # we don't need to modify any other code. _RANDOM_TEST = re.compile(r'#\s*all-random\s+') - # Mark tests to be executed in an external process - currently unsupported. - _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') - def ip2py(self,source): """Convert input IPython source into valid Python.""" block = _ip.input_transformer_manager.transform_cell(source) @@ -182,27 +167,12 @@ def parse(self, string, name=''): terms = list(self._EXAMPLE_RE_PY.finditer(string)) if terms: # Normal Python example - #print '-'*70 # dbg - #print 'PyExample, Source:\n',string # dbg - #print '-'*70 # dbg Example = doctest.Example else: - # It's an ipython example. Note that IPExamples are run - # in-process, so their syntax must be turned into valid python. - # IPExternalExamples are run out-of-process (via pexpect) so they - # don't need any filtering (a real ipython will be executing them). + # It's an ipython example. terms = list(self._EXAMPLE_RE_IP.finditer(string)) - if self._EXTERNAL_IP.search(string): - #print '-'*70 # dbg - #print 'IPExternalExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExternalExample - else: - #print '-'*70 # dbg - #print 'IPExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExample - ip2py = True + Example = IPExample + ip2py = True for m in terms: # Add the pre-example text to `output`. @@ -217,10 +187,6 @@ def parse(self, string, name=''): # cases, it's only non-empty for 'all-random' tests): want += random_marker - if Example is IPExternalExample: - options[doctest.NORMALIZE_WHITESPACE] = True - want += '\n' - # Create an Example, and add it to the list. if not self._IS_BLANK_OR_COMMENT(source): output.append(Example(source, want, exc_msg, From d0c404dac965cf611c08886488b50ae65ceeb5c1 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 26 Nov 2021 18:53:04 +0300 Subject: [PATCH 1755/3726] Make the ipyfunc doctest actually test IPython syntax --- IPython/testing/plugin/simple.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/IPython/testing/plugin/simple.py b/IPython/testing/plugin/simple.py index 3861977cab5..35fbfd2fbdc 100644 --- a/IPython/testing/plugin/simple.py +++ b/IPython/testing/plugin/simple.py @@ -1,7 +1,7 @@ """Simple example using doctests. This file just contains doctests both using plain python and IPython prompts. -All tests should be loaded by nose. +All tests should be loaded by Pytest. """ def pyfunc(): @@ -24,10 +24,21 @@ def pyfunc(): return 'pyfunc' -def ipyfunc2(): - """Some pure python tests... +def ipyfunc(): + """Some IPython tests... + + In [1]: ipyfunc() + Out[1]: 'ipyfunc' + + In [2]: import os + + In [3]: 2+3 + Out[3]: 5 - >>> 1+1 - 2 + In [4]: for i in range(3): + ...: print(i, end=' ') + ...: print(i+1, end=' ') + ...: + Out[4]: 0 1 1 2 2 3 """ - return 'pyfunc2' + return "ipyfunc" From 30e5a5d3346e5caf5a03a2d253084bfbd569ad42 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Fri, 26 Nov 2021 19:40:13 +0300 Subject: [PATCH 1756/3726] deepreload: Use `sys.builtin_module_names` to exclude builtins --- IPython/lib/deepreload.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/IPython/lib/deepreload.py b/IPython/lib/deepreload.py index fa8009b59c1..aaedab24255 100644 --- a/IPython/lib/deepreload.py +++ b/IPython/lib/deepreload.py @@ -282,11 +282,22 @@ def deep_reload_hook(m): original_reload = importlib.reload # Replacement for reload() -def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__', - 'numpy', 'numpy._globals')): +def reload( + module, + exclude=( + *sys.builtin_module_names, + "sys", + "os.path", + "builtins", + "__main__", + "numpy", + "numpy._globals", + ), +): """Recursively reload all modules used in the given module. Optionally takes a list of modules to exclude from reloading. The default exclude - list contains sys, __main__, and __builtin__, to prevent, e.g., resetting + list contains modules listed in sys.builtin_module_names with additional + sys, os.path, builtins and __main__, to prevent, e.g., resetting display, exception, and io hooks. """ global found_now From 08e13a503c9f24176008b2f95ae536ab99fdaf9d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 26 Nov 2021 11:29:13 -0800 Subject: [PATCH 1757/3726] wn 7.16.2 --- docs/source/whatsnew/version7.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 20fa0397220..8edb330a684 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -596,6 +596,19 @@ Which is now also present on subclasses:: .. _version 716: +IPython 7.16.1, 7.16.2 +====================== + +IPython 7.16.1 was release immediately after 7.16.0 to fix a conda packaging issue. +The source is identical to 7.16.0 but the file permissions in the tar are different. + +IPython 7.16.2 pins jedi dependency to "<=0.17.2" which should prevent some +issues for users still on python 3.6. This may not be sufficient as pip may +still allow to downgrade IPython. + +Compatibility with Jedi > 0.17.2 was not added as this would have meant bumping +the minimal version to >0.16. + IPython 7.16 ============ From 2339c33402cacdd8a19b5649ef773cef4f83c713 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 26 Nov 2021 11:55:28 -0800 Subject: [PATCH 1758/3726] What's new 7.30 --- docs/source/whatsnew/version7.rst | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 20fa0397220..e8f94213178 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -2,6 +2,51 @@ 7.x Series ============ +.. _version 7.30: + +IPython 7.30 +============ + +IPython 7.30 fixes a couple of bugs introduce in previous releases (in +particular with respect to path handling), and introduce a few features and +improvements: + +Notably we will highlight :ghpull:`13267` "Document that ``%run`` can execute +notebooks and ipy scripts.", which is the first commit of Fernando Pérez since +mid 2016 (IPython 5.1). If you are new to IPython, Fernando created IPython in +2001. The other most recent contribution of Fernando to IPython itself was +May 2018, by reviewing and merging PRs. I want to note that Fernando is still +active but mostly as a mentor and leader of the whole Jupyter organisation, but +we're still happy to see him contribute code ! + +:ghpull:`13290` "Use sphinxify (if available) in object_inspect_mime path" +should allow richer Repr of docstrings when using jupyterlab inspector. + +:ghpull:`13311` make the debugger use ``ThreadPoolExecutor`` for debugger cmdloop. +This should fix some issues/infinite loop, but let us know if you come across +any regressions. In particular this fixes issues with `kmaork/madbg `_, +a remote debugger for IPython. + +Note that this is likely the ante-penultimate release of IPython 7.x as a stable +branch, as I hope to release IPython 8.0 as well as IPython 7.31 next +month/early 2022. + +IPython 8.0 will drop support for Python 3.7, removed nose as a dependency, and +7.x will only get critical bug fixes with 8.x becoming the new stable. This will +not be possible without `NumFOCUS Small Development Grants +`_ Which allowed us to +hire `Nikita Kniazev `_ who provide Python and C++ +help and contracting work. + + +Many thanks to all the contributors to this release. You can find all individual +contributions to this milestone `on github +`__. + +Thanks as well to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + .. _version 7.29: IPython 7.29 From 7efd021112f91f8a843b6695ca984e50039ea954 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 26 Nov 2021 14:28:02 -0800 Subject: [PATCH 1759/3726] release script zsh compatibility --- tools/release_helper.sh | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/release_helper.sh b/tools/release_helper.sh index 4d5672b498b..a1aa263a8b3 100644 --- a/tools/release_helper.sh +++ b/tools/release_helper.sh @@ -21,7 +21,7 @@ WHITE=$(tput setaf 7) NOR=$(tput sgr0) -echo "Will use $EDITOR to edit files when necessary" +echo "Will use $BLUE'$EDITOR'$NOR to edit files when necessary" echo -n "PREV_RELEASE (X.y.z) [$PREV_RELEASE]: " read input PREV_RELEASE=${input:-$PREV_RELEASE} @@ -37,11 +37,15 @@ BRANCH=${input:-$BRANCH} ask_section(){ echo - echo $BLUE"$1"$NOR + echo $BLUE"$1"$NOR echo -n $GREEN"Press Enter to continue, S to skip: "$NOR - read -n1 value - echo - if [ -z $value ] || [ $value = 'y' ] ; then + if [ "$ZSH_NAME" = "zsh" ] ; then + read -k1 value + value=${value%$'\n'} + else + read -n1 value + fi + if [ -z "$value" ] || [ $value = 'y' ]; then return 0 fi return 1 @@ -51,11 +55,17 @@ ask_section(){ maybe_edit(){ echo echo $BLUE"$1"$NOR - echo -n $GREEN"Press e to Edit $1, any other keys to skip: "$NOR - read -n1 value + echo -n $GREEN"Press ${BLUE}e$GREEN to Edit ${BLUE}$1$GREEN, any other keys to skip: "$NOR + if [ "$ZSH_NAME" = "zsh" ] ; then + read -k1 value + value=${value%$'\n'} + else + read -n1 value + fi + echo if [ $value = 'e' ] ; then - $EDITOR $1 + $=EDITOR $1 fi } @@ -100,7 +110,7 @@ then git checkout $PREV_RELEASE echo $BLUE"Saving API to file $PREV_RELEASE"$NOR frappuccino IPython --save IPython-$PREV_RELEASE.json - echo $BLUE"coming back to $BRANCH"$NOR + echo $BLUE"comming back to $BRANCH"$NOR git checkout $BRANCH echo $BLUE"comparing ..."$NOR frappuccino IPython --compare IPython-$PREV_RELEASE.json @@ -116,7 +126,7 @@ echo $GREEN"please update version number in ${RED}IPython/core/release.py${NOR} echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py${NOR}" sed -i bkp -e '/Uncomment/s/^# //g' IPython/core/release.py rm IPython/core/release.pybkp -git diff +git diff | cat maybe_edit IPython/core/release.py echo $GREEN"Press enter to continue"$NOR @@ -163,7 +173,7 @@ then echo $GREEN"I tried ${RED}sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py${NOR}" sed -i bkp -e '/Uncomment/s/^/# /g' IPython/core/release.py rm IPython/core/release.pybkp - git diff + git diff | cat echo $GREEN"Please bump ${RED}the minor version number${NOR}" maybe_edit IPython/core/release.py echo ${BLUE}"Do not commit yet – we'll do it later."$NOR From fca0f37b6e11ca5603328430b80401444165c9f3 Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Fri, 26 Nov 2021 15:29:09 -0800 Subject: [PATCH 1760/3726] avoid mutable default argument! --- IPython/core/interactiveshell.py | 2 +- IPython/core/oinspect.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 7d3d00300ea..8390d025d46 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1804,7 +1804,7 @@ def object_inspect_text(self, oname, detail_level=0): """Get object info as formatted text""" return self.object_inspect_mime(oname, detail_level)['text/plain'] - def object_inspect_mime(self, oname, detail_level=0, omit_sections={}): + def object_inspect_mime(self, oname, detail_level=0, omit_sections=()): """Get object info as a mimebundle of formatted representations. A mimebundle is a dictionary, keyed by mime-type. diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 74b3cc9916a..3f12d519d7a 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -567,7 +567,7 @@ def format_mime(self, bundle): return bundle def _get_info( - self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections={} + self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=() ): """Retrieve an info dict and format it. @@ -583,8 +583,8 @@ def _get_info( already computed information detail_level: integer Granularity of detail level, if set to 1, give more information. - omit_sections: set[str] - Titles or keys to omit from output + omit_sections: container[str] + Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`) """ info = self._info(obj, oname=oname, info=info, detail_level=detail_level) @@ -669,7 +669,7 @@ def pinfo( info=None, detail_level=0, enable_html_pager=True, - omit_sections={}, + omit_sections=(), ): """Show detailed information about an object. From 575be23d4994cabb0ad7b9469a319c1758b10a15 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 27 Nov 2021 10:11:01 -0800 Subject: [PATCH 1761/3726] Don't add ipython unconditionally to sys.path Load ext will already do it with a context manager. This should remove false positive when import modules, whithout breaking functionalities. Maybe closes #13294 (making this part of 8.0 release make sens). --- IPython/core/application.py | 40 +++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/IPython/core/application.py b/IPython/core/application.py index 85c91274d4e..3a1797ab090 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -185,6 +185,15 @@ def _profile_changed(self, change): get_ipython_package_dir(), u'config', u'profile', change['new'] ) + add_ipython_dir_to_sys_path = Bool(False, + """Should the IPython profile directory be added to sys path ? + + This option was non-existing before IPython 8.0, and ipython_dir was added to + sys path to allow import of extensions present there. This was historical + baggage from when pip did not exist. This now default to false, + but can bet to true for legacy reasons + """).tag(config=True) + ipython_dir = Unicode( help=""" The name of the IPython directory. This directory is used for logging @@ -294,21 +303,22 @@ def _ipython_dir_changed(self, change): str_old = os.path.abspath(old) if str_old in sys.path: sys.path.remove(str_old) - str_path = os.path.abspath(new) - sys.path.append(str_path) - ensure_dir_exists(new) - readme = os.path.join(new, 'README') - readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') - if not os.path.exists(readme) and os.path.exists(readme_src): - shutil.copy(readme_src, readme) - for d in ('extensions', 'nbextensions'): - path = os.path.join(new, d) - try: - ensure_dir_exists(path) - except OSError as e: - # this will not be EEXIST - self.log.error("couldn't create path %s: %s", path, e) - self.log.debug("IPYTHONDIR set to: %s" % new) + if self.add_ipython_dir_to_sys_path: + str_path = os.path.abspath(new) + #sys.path.append(str_path) + ensure_dir_exists(new) + readme = os.path.join(new, 'README') + readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') + if not os.path.exists(readme) and os.path.exists(readme_src): + shutil.copy(readme_src, readme) + for d in ('extensions', 'nbextensions'): + path = os.path.join(new, d) + try: + ensure_dir_exists(path) + except OSError as e: + # this will not be EEXIST + self.log.error("couldn't create path %s: %s", path, e) + self.log.debug("IPYTHONDIR set to: %s" % new) def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): """Load the config file. From 3147552907530e46e28b4684fbe20c61d00fe061 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 1 Dec 2021 09:19:52 +0100 Subject: [PATCH 1762/3726] avoid deprecated asyncio.get_event_loop get_running_loop is only valid from inside a coroutine EventLoopPolicy.get_event_loop is _not_ deprecated --- IPython/core/async_helpers.py | 17 ++++++++++++++--- IPython/core/tests/test_interactiveshell.py | 16 ++++++++++++++++ IPython/core/tests/test_magic.py | 6 +++++- IPython/terminal/interactiveshell.py | 11 +++++++---- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 72220511838..6623fae4e13 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -12,17 +12,28 @@ import ast +import asyncio import inspect class _AsyncIORunner: + def __init__(self): + self._loop = None + + @property + def loop(self): + """Always returns a non-closed event loop""" + if self._loop is None or self._loop.is_closed(): + policy = asyncio.get_event_loop_policy() + self._loop = policy.new_event_loop() + policy.set_event_loop(self._loop) + return self._loop + def __call__(self, coro): """ Handler for asyncio autoawait """ - import asyncio - - return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro) + return self.loop.run_until_complete(coro) def __str__(self): return "asyncio" diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index a3a55fc66af..355f2b00159 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -1058,6 +1058,22 @@ def test_run_cell_async(): assert result.result == 5 +def test_run_cell_await(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 10") + assert ip.user_ns["_"] == 10 + + +def test_run_cell_asyncio_run(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 1") + assert ip.user_ns["_"] == 1 + result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2") + assert ip.user_ns["_"] == 2 + result = ip.run_cell("await asyncio.sleep(0.01); 3") + assert ip.user_ns["_"] == 3 + + def test_should_run_async(): assert not ip.should_run_async("a = 5") assert ip.should_run_async("await x") diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index fdd6d90445f..641a6ff66a1 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -970,7 +970,11 @@ def test_script_config(): @pytest.fixture def event_loop(): - yield asyncio.get_event_loop_policy().get_event_loop() + policy = asyncio.get_event_loop_policy() + loop = policy.new_event_loop() + policy.set_event_loop(loop) + yield loop + loop.close() @dec.skip_win32 diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 7207a572408..5e7d95fcfc0 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -504,13 +504,15 @@ def prompt_for_code(self): # If we don't do this, people could spawn coroutine with a # while/true inside which will freeze the prompt. + policy = asyncio.get_event_loop_policy() try: - old_loop = asyncio.get_running_loop() + old_loop = policy.get_event_loop() except RuntimeError: - # This happens when the user used `asyncio.run()`. + # This happens when the the event loop is closed, + # e.g. by calling `asyncio.run()`. old_loop = None - asyncio.set_event_loop(self.pt_loop) + policy.set_event_loop(self.pt_loop) try: with patch_stdout(raw=True): text = self.pt_app.prompt( @@ -518,7 +520,8 @@ def prompt_for_code(self): **self._extra_prompt_options()) finally: # Restore the original event loop. - asyncio.set_event_loop(old_loop) + if old_loop is not None: + policy.set_event_loop(old_loop) return text From 0fbbb2b59969d43048c0da19dfc073238bfd00bd Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 1 Dec 2021 12:36:16 +0100 Subject: [PATCH 1763/3726] handle closed event loop in async script magics leave other problems with asyncio implementation for another PR --- IPython/core/magics/script.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py index 7876e648674..dab63e742d8 100644 --- a/IPython/core/magics/script.py +++ b/IPython/core/magics/script.py @@ -81,7 +81,15 @@ def safe_watcher(): yield return - loop = policy.get_event_loop() + try: + loop = policy.get_event_loop() + if loop.is_closed(): + raise RuntimeError("open a new one") + except RuntimeError: + # closed loop, make a new one + loop = policy.new_event_loop() + policy.set_event_loop(loop) + try: watcher = asyncio.SafeChildWatcher() watcher.attach_loop(loop) @@ -236,9 +244,19 @@ async def _stream_communicate(process, cell): await asyncio.wait([stdout_task, stderr_task]) await process.wait() - if sys.platform.startswith("win"): - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - loop = asyncio.get_event_loop_policy().get_event_loop() + policy = asyncio.get_event_loop_policy() + if sys.platform.startswith("win") and not isinstance( + policy, asyncio.WindowsProactorEventLoopPolicy + ): + # _do not_ overwrite the current policy + policy = asyncio.WindowsProactorEventLoopPolicy() + + try: + loop = policy.get_event_loop() + except RuntimeError: + # closed loop, make a new one + loop = policy.new_event_loop() + policy.set_event_loop(loop) argv = arg_split(line, posix=not sys.platform.startswith("win")) args, cmd = self.shebang.parser.parse_known_args(argv) try: From 02ce77ec516a3df7086205a771247a591c319c58 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 1 Dec 2021 17:30:26 +0300 Subject: [PATCH 1764/3726] Remove parso xfail for Python>=3.11 parso 0.8.3 added basic support for Python 3.11 and 3.12 grammars --- IPython/core/tests/test_completer.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 4feba09a651..eece34a4790 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -210,11 +210,6 @@ def _ipython_key_completions_(self): return list(self.things) -@pytest.mark.xfail( - sys.version_info >= (3, 11), - reason="parso does not support 3.11 yet", - raises=NotImplementedError, -) class TestCompleter(unittest.TestCase): def setUp(self): """ From cc76a77c3ea479d267d558ac3c4b7bbb101ea860 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Wed, 1 Dec 2021 17:34:30 +0300 Subject: [PATCH 1765/3726] Remove jedi<=0.18.0 xfail for Python>=3.10 jedi 0.18.1 fixed the issue --- IPython/core/tests/test_completer.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index eece34a4790..59f8fee0e67 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -26,15 +26,6 @@ _deduplicate_completions, ) -if sys.version_info >= (3, 10): - import jedi - from pkg_resources import parse_version - - # Requires https://github.com/davidhalter/jedi/pull/1795 - jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0") -else: - jedi_issue = False - # ----------------------------------------------------------------------------- # Test functions # ----------------------------------------------------------------------------- @@ -430,8 +421,6 @@ def test_all_completions_dups(self): matches = c.all_completions("TestCl") assert matches == ['TestClass'], jedi_status matches = c.all_completions("TestClass.") - if jedi_status and jedi_issue: - continue assert len(matches) > 2, jedi_status matches = c.all_completions("TestClass.a") assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status @@ -486,7 +475,6 @@ def test_completion_have_signature(self): "encoding" in c.signature ), "Signature of function was not found by completer" - @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0") def test_deduplicate_completions(self): """ Test that completions are correctly deduplicated (even if ranges are not the same) From 73ffd1ba1d3e83b0611df6ec0f5ec3eb12c5bf30 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 11:51:18 -0800 Subject: [PATCH 1766/3726] Apply suggestions from code review Co-authored-by: Min RK --- IPython/core/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/core/application.py b/IPython/core/application.py index 3a1797ab090..337f7ba7f7c 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -191,7 +191,7 @@ def _profile_changed(self, change): This option was non-existing before IPython 8.0, and ipython_dir was added to sys path to allow import of extensions present there. This was historical baggage from when pip did not exist. This now default to false, - but can bet to true for legacy reasons + but can be set to true for legacy reasons. """).tag(config=True) ipython_dir = Unicode( @@ -305,7 +305,7 @@ def _ipython_dir_changed(self, change): sys.path.remove(str_old) if self.add_ipython_dir_to_sys_path: str_path = os.path.abspath(new) - #sys.path.append(str_path) + sys.path.append(str_path) ensure_dir_exists(new) readme = os.path.join(new, 'README') readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') From 96c5d75c72131f888b8d3f9dc95a7fc35ee68808 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 11:52:17 -0800 Subject: [PATCH 1767/3726] reformat --- IPython/core/application.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/IPython/core/application.py b/IPython/core/application.py index 337f7ba7f7c..c9e8e6f4d43 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -185,14 +185,16 @@ def _profile_changed(self, change): get_ipython_package_dir(), u'config', u'profile', change['new'] ) - add_ipython_dir_to_sys_path = Bool(False, + add_ipython_dir_to_sys_path = Bool( + False, """Should the IPython profile directory be added to sys path ? This option was non-existing before IPython 8.0, and ipython_dir was added to sys path to allow import of extensions present there. This was historical baggage from when pip did not exist. This now default to false, but can be set to true for legacy reasons. - """).tag(config=True) + """, + ).tag(config=True) ipython_dir = Unicode( help=""" @@ -307,11 +309,13 @@ def _ipython_dir_changed(self, change): str_path = os.path.abspath(new) sys.path.append(str_path) ensure_dir_exists(new) - readme = os.path.join(new, 'README') - readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') + readme = os.path.join(new, "README") + readme_src = os.path.join( + get_ipython_package_dir(), "config", "profile", "README" + ) if not os.path.exists(readme) and os.path.exists(readme_src): shutil.copy(readme_src, readme) - for d in ('extensions', 'nbextensions'): + for d in ("extensions", "nbextensions"): path = os.path.join(new, d) try: ensure_dir_exists(path) From 43c7193695c51122dfdf68bdcab2a2d2c65a4d3d Mon Sep 17 00:00:00 2001 From: 007vedant Date: Wed, 24 Nov 2021 18:20:40 +0530 Subject: [PATCH 1768/3726] Added custom pprint to pretty print collections.UserList like regular lists. -implemented _userlist_pprint , in accordance with pprint of other container types of collections module. --- IPython/lib/pretty.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 7a18f01c6cd..3a933d2defb 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -920,10 +920,20 @@ def _counter_pprint(obj, p, cycle): else: p.pretty(cls_ctor()) + +def _userlist_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(list(obj))) + + for_type_by_name('collections', 'defaultdict', _defaultdict_pprint) for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint) for_type_by_name('collections', 'deque', _deque_pprint) for_type_by_name('collections', 'Counter', _counter_pprint) +for_type_by_name("collections", "UserList", _userlist_pprint) if __name__ == '__main__': from random import randrange From b48dbbb6bbf0e471acd373a3a67a28b910bb29ca Mon Sep 17 00:00:00 2001 From: 007vedant Date: Sat, 27 Nov 2021 19:31:36 +0530 Subject: [PATCH 1769/3726] Updated _userlist_print with obj.data for optimization --- IPython/lib/pretty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 3a933d2defb..72f143522df 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -926,7 +926,7 @@ def _userlist_pprint(obj, p, cycle): if cycle: p.pretty(cls_ctor(RawText("..."))) else: - p.pretty(cls_ctor(list(obj))) + p.pretty(cls_ctor(obj.data)) for_type_by_name('collections', 'defaultdict', _defaultdict_pprint) From aca50d203f176c060236720e749f1d83dfe9152c Mon Sep 17 00:00:00 2001 From: 007vedant Date: Sat, 27 Nov 2021 19:35:50 +0530 Subject: [PATCH 1770/3726] Added test for _userlist_pprint - all tests passed --- IPython/lib/tests/test_pretty.py | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 8b8a6ee75f6..ca16924e8f7 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -5,7 +5,7 @@ # Distributed under the terms of the Modified BSD License. -from collections import Counter, defaultdict, deque, OrderedDict +from collections import Counter, defaultdict, deque, OrderedDict, UserList import os import pytest import types @@ -294,6 +294,42 @@ def type_pprint_wrapper(obj, p, cycle): assert type_pprint_wrapper.called is True +def test_collections_userlist(): + # Create userlist with cycle + a = UserList() + a.append(a) + + cases = [ + (UserList(), "UserList([])"), + ( + UserList(i for i in range(1000, 1020)), + "UserList([1000,\n" + " 1001,\n" + " 1002,\n" + " 1003,\n" + " 1004,\n" + " 1005,\n" + " 1006,\n" + " 1007,\n" + " 1008,\n" + " 1009,\n" + " 1010,\n" + " 1011,\n" + " 1012,\n" + " 1013,\n" + " 1014,\n" + " 1015,\n" + " 1016,\n" + " 1017,\n" + " 1018,\n" + " 1019])", + ), + (a, "UserList([UserList(...)])"), + ] + for obj, expected in cases: + assert pretty.pretty(obj) == expected + + # TODO : pytest.mark.parametrise once nose is gone. def test_collections_defaultdict(): # Create defaultdicts with cycles From 967e96ec53b7e50ac3253fa7e26af328978b073a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 15:00:13 -0800 Subject: [PATCH 1771/3726] remove code deprecated since at least IPython 5.0 --- .github/workflows/test.yml | 2 +- IPython/core/interactiveshell.py | 57 ----------------------- IPython/core/tests/test_magic_terminal.py | 6 +-- IPython/terminal/magics.py | 9 ++-- IPython/utils/io.py | 5 -- 5 files changed, 8 insertions(+), 71 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2620162c209..898dfe5f8d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,6 @@ jobs: env: COLUMNS: 120 run: | - pytest --color=yes -ra -v --cov --cov-report=xml + pytest --color=yes -raXxs --cov --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index a911c3e2e96..b48b572ad17 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -161,13 +161,6 @@ def no_op(*a, **kw): class SpaceInInput(Exception): pass -def get_default_colors(): - "DEPRECATED" - warn('get_default_color is deprecated since IPython 5.0, and returns `Neutral` on all platforms.', - DeprecationWarning, stacklevel=2) - return 'Neutral' - - class SeparateUnicode(Unicode): r"""A Unicode subclass to validate separate_in, separate_out, etc. @@ -440,29 +433,6 @@ def input_splitter(self): will be displayed as regular output instead.""" ).tag(config=True) - # deprecated prompt traits: - - prompt_in1 = Unicode('In [\\#]: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompt_in2 = Unicode(' .\\D.: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompt_out = Unicode('Out[\\#]: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompts_pad_left = Bool(True, - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - - @observe('prompt_in1', 'prompt_in2', 'prompt_out', 'prompt_pad_left') - def _prompt_trait_changed(self, change): - name = change['name'] - warn("InteractiveShell.{name} is deprecated since IPython 4.0" - " and ignored since 5.0, set TerminalInteractiveShell.prompts" - " object directly.".format(name=name)) - - # protect against weird cases where self.config may not exist: show_rewritten_input = Bool(True, help="Show rewritten input, e.g. for autocall." @@ -2038,19 +2008,6 @@ def showindentationerror(self): the %paste magic.""" self.showsyntaxerror() - #------------------------------------------------------------------------- - # Things related to readline - #------------------------------------------------------------------------- - - def init_readline(self): - """DEPRECATED - - Moved to terminal subclass, here only to simplify the init logic.""" - # Set a number of methods that depend on readline to be no-op - warnings.warn('`init_readline` is no-op since IPython 5.0 and is Deprecated', - DeprecationWarning, stacklevel=2) - self.set_custom_completer = no_op - @skip_doctest def set_next_input(self, s, replace=False): """ Sets the 'default' input string for the next command line. @@ -3513,20 +3470,6 @@ def mktempfile(self, data=None, prefix='ipython_edit_'): file_path.write_text(data) return filename - @undoc - def write(self,data): - """DEPRECATED: Write a string to the default output""" - warn('InteractiveShell.write() is deprecated, use sys.stdout instead', - DeprecationWarning, stacklevel=2) - sys.stdout.write(data) - - @undoc - def write_err(self,data): - """DEPRECATED: Write a string to the default error output""" - warn('InteractiveShell.write_err() is deprecated, use sys.stderr instead', - DeprecationWarning, stacklevel=2) - sys.stderr.write(data) - def ask_yes_no(self, prompt, default=None, interrupt=None): if self.quiet: return True diff --git a/IPython/core/tests/test_magic_terminal.py b/IPython/core/tests/test_magic_terminal.py index d7984280d75..721fd5eda4d 100644 --- a/IPython/core/tests/test_magic_terminal.py +++ b/IPython/core/tests/test_magic_terminal.py @@ -149,8 +149,8 @@ def test_paste_email_py(self): def test_paste_echo(self): "Also test self.paste echoing, by temporarily faking the writer" w = StringIO() - writer = ip.write - ip.write = w.write + old_write = sys.stdout.write + sys.stdout.write = w.write code = """ a = 100 b = 200""" @@ -158,7 +158,7 @@ def test_paste_echo(self): self.paste(code,'') out = w.getvalue() finally: - ip.write = writer + sys.stdout.write = old_write self.assertEqual(ip.user_ns["a"], 100) self.assertEqual(ip.user_ns["b"], 200) assert out == code + "\n## -- End pasted text --\n" diff --git a/IPython/terminal/magics.py b/IPython/terminal/magics.py index 38842231f0d..aef96071f0e 100644 --- a/IPython/terminal/magics.py +++ b/IPython/terminal/magics.py @@ -198,11 +198,10 @@ def paste(self, parameter_s=''): # By default, echo back to terminal unless quiet mode is requested if 'q' not in opts: - write = self.shell.write - write(self.shell.pycolorize(block)) - if not block.endswith('\n'): - write('\n') - write("## -- End pasted text --\n") + sys.stdout.write(self.shell.pycolorize(block)) + if not block.endswith("\n"): + sys.stdout.write("\n") + sys.stdout.write("## -- End pasted text --\n") self.store_or_execute(block, name) diff --git a/IPython/utils/io.py b/IPython/utils/io.py index 1600fc32106..638471e8fbb 100644 --- a/IPython/utils/io.py +++ b/IPython/utils/io.py @@ -233,11 +233,6 @@ def raw_print_err(*args, **kw): file=sys.__stderr__) sys.__stderr__.flush() -# used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added -# Keep for a version or two then should remove -rprint = raw_print -rprinte = raw_print_err - @undoc def unicode_std_stream(stream='stdout'): """DEPRECATED, moved to nbconvert.utils.io""" From 7d4e0677bd43dba83a71d2e48cbd98ab1812639c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 15:43:17 -0800 Subject: [PATCH 1772/3726] Remove all the lib/inputhook logic and related files. 1) for terminal ipython hooks are in pt_inputhook, 2) for ipykernel the inputhook are in ipykernel itself. --- IPython/lib/inputhook.py | 663 ---------------------- IPython/lib/inputhookglut.py | 172 ------ IPython/lib/inputhookgtk.py | 35 -- IPython/lib/inputhookgtk3.py | 34 -- IPython/lib/inputhookgtk4.py | 43 -- IPython/lib/inputhookpyglet.py | 111 ---- IPython/lib/inputhookqt4.py | 180 ------ IPython/lib/inputhookwx.py | 167 ------ codecov.yml | 1 - examples/IPython Kernel/gui/gui-gtk.py | 39 -- examples/IPython Kernel/gui/gui-gtk3.py | 37 -- examples/IPython Kernel/gui/gui-pyglet.py | 33 -- examples/IPython Kernel/gui/gui-tk.py | 34 -- examples/IPython Kernel/gui/gui-wx.py | 106 ---- pytest.ini | 1 - 15 files changed, 1656 deletions(-) delete mode 100644 IPython/lib/inputhook.py delete mode 100644 IPython/lib/inputhookglut.py delete mode 100644 IPython/lib/inputhookgtk.py delete mode 100644 IPython/lib/inputhookgtk3.py delete mode 100644 IPython/lib/inputhookgtk4.py delete mode 100644 IPython/lib/inputhookpyglet.py delete mode 100644 IPython/lib/inputhookqt4.py delete mode 100644 IPython/lib/inputhookwx.py delete mode 100755 examples/IPython Kernel/gui/gui-gtk.py delete mode 100644 examples/IPython Kernel/gui/gui-gtk3.py delete mode 100644 examples/IPython Kernel/gui/gui-pyglet.py delete mode 100755 examples/IPython Kernel/gui/gui-tk.py delete mode 100755 examples/IPython Kernel/gui/gui-wx.py diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py deleted file mode 100644 index 86077d069df..00000000000 --- a/IPython/lib/inputhook.py +++ /dev/null @@ -1,663 +0,0 @@ -# coding: utf-8 -""" -Deprecated since IPython 5.0 - -Inputhook management for GUI event loop integration. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -try: - import ctypes -except ImportError: - ctypes = None -except SystemError: # IronPython issue, 2/8/2014 - ctypes = None -import os -import platform -import sys -from distutils.version import LooseVersion as V - -from warnings import warn - - -warn("`IPython.lib.inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - -# Constants for identifying the GUI toolkits. -GUI_WX = 'wx' -GUI_QT = 'qt' -GUI_QT4 = 'qt4' -GUI_GTK = 'gtk' -GUI_TK = 'tk' -GUI_OSX = 'osx' -GUI_GLUT = 'glut' -GUI_PYGLET = 'pyglet' -GUI_GTK3 = 'gtk3' -GUI_NONE = 'none' # i.e. disable - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- - -def _stdin_ready_posix(): - """Return True if there's something to read on stdin (posix version).""" - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - return bool(infds) - -def _stdin_ready_nt(): - """Return True if there's something to read on stdin (nt version).""" - return msvcrt.kbhit() - -def _stdin_ready_other(): - """Return True, assuming there's something to read on stdin.""" - return True - -def _use_appnope(): - """Should we use appnope for dealing with OS X app nap? - - Checks if we are on OS X 10.9 or greater. - """ - return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') - -def _ignore_CTRL_C_posix(): - """Ignore CTRL+C (SIGINT).""" - signal.signal(signal.SIGINT, signal.SIG_IGN) - -def _allow_CTRL_C_posix(): - """Take CTRL+C into account (SIGINT).""" - signal.signal(signal.SIGINT, signal.default_int_handler) - -def _ignore_CTRL_C_other(): - """Ignore CTRL+C (not implemented).""" - pass - -def _allow_CTRL_C_other(): - """Take CTRL+C into account (not implemented).""" - pass - -if os.name == 'posix': - import select - import signal - stdin_ready = _stdin_ready_posix - ignore_CTRL_C = _ignore_CTRL_C_posix - allow_CTRL_C = _allow_CTRL_C_posix -elif os.name == 'nt': - import msvcrt - stdin_ready = _stdin_ready_nt - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other -else: - stdin_ready = _stdin_ready_other - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other - - -#----------------------------------------------------------------------------- -# Main InputHookManager class -#----------------------------------------------------------------------------- - - -class InputHookManager(object): - """DEPRECATED since IPython 5.0 - - Manage PyOS_InputHook for different GUI toolkits. - - This class installs various hooks under ``PyOSInputHook`` to handle - GUI event loop integration. - """ - - def __init__(self): - if ctypes is None: - warn("IPython GUI event loop requires ctypes, %gui will not be available") - else: - self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int) - self.guihooks = {} - self.aliases = {} - self.apps = {} - self._reset() - - def _reset(self): - self._callback_pyfunctype = None - self._callback = None - self._installed = False - self._current_gui = None - - def get_pyos_inputhook(self): - """DEPRECATED since IPython 5.0 - - Return the current PyOS_InputHook as a ctypes.c_void_p.""" - warn("`get_pyos_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook") - - def get_pyos_inputhook_as_func(self): - """DEPRECATED since IPython 5.0 - - Return the current PyOS_InputHook as a ctypes.PYFUNCYPE.""" - warn("`get_pyos_inputhook_as_func` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook") - - def set_inputhook(self, callback): - """DEPRECATED since IPython 5.0 - - Set PyOS_InputHook to callback and return the previous one.""" - # On platforms with 'readline' support, it's all too likely to - # have a KeyboardInterrupt signal delivered *even before* an - # initial ``try:`` clause in the callback can be executed, so - # we need to disable CTRL+C in this situation. - ignore_CTRL_C() - self._callback = callback - self._callback_pyfunctype = self.PYFUNC(callback) - pyos_inputhook_ptr = self.get_pyos_inputhook() - original = self.get_pyos_inputhook_as_func() - pyos_inputhook_ptr.value = \ - ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value - self._installed = True - return original - - def clear_inputhook(self, app=None): - """DEPRECATED since IPython 5.0 - - Set PyOS_InputHook to NULL and return the previous one. - - Parameters - ---------- - app : optional, ignored - This parameter is allowed only so that clear_inputhook() can be - called with a similar interface as all the ``enable_*`` methods. But - the actual value of the parameter is ignored. This uniform interface - makes it easier to have user-level entry points in the main IPython - app like :meth:`enable_gui`.""" - warn("`clear_inputhook` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - pyos_inputhook_ptr = self.get_pyos_inputhook() - original = self.get_pyos_inputhook_as_func() - pyos_inputhook_ptr.value = ctypes.c_void_p(None).value - allow_CTRL_C() - self._reset() - return original - - def clear_app_refs(self, gui=None): - """DEPRECATED since IPython 5.0 - - Clear IPython's internal reference to an application instance. - - Whenever we create an app for a user on qt4 or wx, we hold a - reference to the app. This is needed because in some cases bad things - can happen if a user doesn't hold a reference themselves. This - method is provided to clear the references we are holding. - - Parameters - ---------- - gui : None or str - If None, clear all app references. If ('wx', 'qt4') clear - the app for that toolkit. References are not held for gtk or tk - as those toolkits don't have the notion of an app. - """ - warn("`clear_app_refs` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if gui is None: - self.apps = {} - elif gui in self.apps: - del self.apps[gui] - - def register(self, toolkitname, *aliases): - """DEPRECATED since IPython 5.0 - - Register a class to provide the event loop for a given GUI. - - This is intended to be used as a class decorator. It should be passed - the names with which to register this GUI integration. The classes - themselves should subclass :class:`InputHookBase`. - - :: - - @inputhook_manager.register('qt') - class QtInputHook(InputHookBase): - def enable(self, app=None): - ... - """ - warn("`register` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - def decorator(cls): - if ctypes is not None: - inst = cls(self) - self.guihooks[toolkitname] = inst - for a in aliases: - self.aliases[a] = toolkitname - return cls - return decorator - - def current_gui(self): - """DEPRECATED since IPython 5.0 - - Return a string indicating the currently active GUI or None.""" - warn("`current_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - return self._current_gui - - def enable_gui(self, gui=None, app=None): - """DEPRECATED since IPython 5.0 - - Switch amongst GUI input hooks by name. - - This is a higher level method than :meth:`set_inputhook` - it uses the - GUI name to look up a registered object which enables the input hook - for that GUI. - - Parameters - ---------- - gui : optional, string or None - If None (or 'none'), clears input hook, otherwise it must be one - of the recognized GUI names (see ``GUI_*`` constants in module). - - app : optional, existing application object. - For toolkits that have the concept of a global app, you can supply an - existing one. If not given, the toolkit will be probed for one, and if - none is found, a new one will be created. Note that GTK does not have - this concept, and passing an app if ``gui=="GTK"`` will raise an error. - - Returns - ------- - The output of the underlying gui switch routine, typically the actual - PyOS_InputHook wrapper object or the GUI toolkit app created, if there was - one. - """ - warn("`enable_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if gui in (None, GUI_NONE): - return self.disable_gui() - - if gui in self.aliases: - return self.enable_gui(self.aliases[gui], app) - - try: - gui_hook = self.guihooks[gui] - except KeyError as e: - e = "Invalid GUI request {!r}, valid ones are: {}" - raise ValueError(e.format(gui, ', '.join(self.guihooks))) from e - self._current_gui = gui - - app = gui_hook.enable(app) - if app is not None: - app._in_event_loop = True - self.apps[gui] = app - return app - - def disable_gui(self): - """DEPRECATED since IPython 5.0 - - Disable GUI event loop integration. - - If an application was registered, this sets its ``_in_event_loop`` - attribute to False. It then calls :meth:`clear_inputhook`. - """ - warn("`disable_gui` is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - gui = self._current_gui - if gui in self.apps: - self.apps[gui]._in_event_loop = False - return self.clear_inputhook() - -class InputHookBase(object): - """DEPRECATED since IPython 5.0 - - Base class for input hooks for specific toolkits. - - Subclasses should define an :meth:`enable` method with one argument, ``app``, - which will either be an instance of the toolkit's application class, or None. - They may also define a :meth:`disable` method with no arguments. - """ - def __init__(self, manager): - self.manager = manager - - def disable(self): - pass - -inputhook_manager = InputHookManager() - -@inputhook_manager.register('osx') -class NullInputHook(InputHookBase): - """DEPRECATED since IPython 5.0 - - A null inputhook that doesn't need to do anything""" - def enable(self, app=None): - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - -@inputhook_manager.register('wx') -class WxInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with wxPython. - - Parameters - ---------- - app : WX Application, optional. - Running application to use. If not given, we probe WX for an - existing application object, and create a new one if none is found. - - Notes - ----- - This methods sets the ``PyOS_InputHook`` for wxPython, which allows - the wxPython to integrate with terminal based applications like - IPython. - - If ``app`` is not given we probe for an existing one, and return it if - found. If no existing app is found, we create an :class:`wx.App` as - follows:: - - import wx - app = wx.App(redirect=False, clearSigInt=False) - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import wx - - wx_version = V(wx.__version__).version - - if wx_version < [2, 8]: - raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__) - - from IPython.lib.inputhookwx import inputhook_wx - self.manager.set_inputhook(inputhook_wx) - if _use_appnope(): - from appnope import nope - nope() - - import wx - if app is None: - app = wx.GetApp() - if app is None: - app = wx.App(redirect=False, clearSigInt=False) - - return app - - def disable(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with wxPython. - - This restores appnapp on OS X - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if _use_appnope(): - from appnope import nap - nap() - -@inputhook_manager.register('qt', 'qt4') -class Qt4InputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with PyQt4. - - Parameters - ---------- - app : Qt Application, optional. - Running application to use. If not given, we probe Qt for an - existing application object, and create a new one if none is found. - - Notes - ----- - This methods sets the PyOS_InputHook for PyQt4, which allows - the PyQt4 to integrate with terminal based applications like - IPython. - - If ``app`` is not given we probe for an existing one, and return it if - found. If no existing app is found, we create an :class:`QApplication` - as follows:: - - from PyQt4 import QtCore - app = QtGui.QApplication(sys.argv) - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookqt4 import create_inputhook_qt4 - app, inputhook_qt4 = create_inputhook_qt4(self.manager, app) - self.manager.set_inputhook(inputhook_qt4) - if _use_appnope(): - from appnope import nope - nope() - - return app - - def disable_qt4(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with PyQt4. - - This restores appnapp on OS X - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if _use_appnope(): - from appnope import nap - nap() - - -@inputhook_manager.register('qt5') -class Qt5InputHook(Qt4InputHook): - def enable(self, app=None): - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - os.environ['QT_API'] = 'pyqt5' - return Qt4InputHook.enable(self, app) - - -@inputhook_manager.register('gtk') -class GtkInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with PyGTK. - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the PyOS_InputHook for PyGTK, which allows - the PyGTK to integrate with terminal based applications like - IPython. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import gtk - try: - gtk.set_interactive(True) - except AttributeError: - # For older versions of gtk, use our own ctypes version - from IPython.lib.inputhookgtk import inputhook_gtk - self.manager.set_inputhook(inputhook_gtk) - - -@inputhook_manager.register('tk') -class TkInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with Tk. - - Parameters - ---------- - app : toplevel :class:`Tkinter.Tk` widget, optional. - Running toplevel widget to use. If not given, we probe Tk for an - existing one, and create a new one if none is found. - - Notes - ----- - If you have already created a :class:`Tkinter.Tk` object, the only - thing done by this method is to register with the - :class:`InputHookManager`, since creating that object automatically - sets ``PyOS_InputHook``. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - if app is None: - from tkinter import Tk - app = Tk() - app.withdraw() - self.manager.apps[GUI_TK] = app - return app - - -@inputhook_manager.register('glut') -class GlutInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with GLUT. - - Parameters - ---------- - - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - - This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to - integrate with terminal based applications like IPython. Due to GLUT - limitations, it is currently not possible to start the event loop - without first creating a window. You should thus not create another - window but use instead the created one. See 'gui-glut.py' in the - docs/examples/lib directory. - - The default screen mode is set to: - glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - - import OpenGL.GLUT as glut - from IPython.lib.inputhookglut import glut_display_mode, \ - glut_close, glut_display, \ - glut_idle, inputhook_glut - - if GUI_GLUT not in self.manager.apps: - glut.glutInit( sys.argv ) - glut.glutInitDisplayMode( glut_display_mode ) - # This is specific to freeglut - if bool(glut.glutSetOption): - glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, - glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) - glut.glutCreateWindow( sys.argv[0] ) - glut.glutReshapeWindow( 1, 1 ) - glut.glutHideWindow( ) - glut.glutWMCloseFunc( glut_close ) - glut.glutDisplayFunc( glut_display ) - glut.glutIdleFunc( glut_idle ) - else: - glut.glutWMCloseFunc( glut_close ) - glut.glutDisplayFunc( glut_display ) - glut.glutIdleFunc( glut_idle) - self.manager.set_inputhook( inputhook_glut ) - - - def disable(self): - """DEPRECATED since IPython 5.0 - - Disable event loop integration with glut. - - This sets PyOS_InputHook to NULL and set the display function to a - dummy one and set the timer to a dummy timer that will be triggered - very far in the future. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - import OpenGL.GLUT as glut - from glut_support import glutMainLoopEvent - - glut.glutHideWindow() # This is an event to be processed below - glutMainLoopEvent() - super(GlutInputHook, self).disable() - -@inputhook_manager.register('pyglet') -class PygletInputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with pyglet. - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the ``PyOS_InputHook`` for pyglet, which allows - pyglet to integrate with terminal based applications like - IPython. - - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookpyglet import inputhook_pyglet - self.manager.set_inputhook(inputhook_pyglet) - return app - - -@inputhook_manager.register('gtk3') -class Gtk3InputHook(InputHookBase): - def enable(self, app=None): - """DEPRECATED since IPython 5.0 - - Enable event loop integration with Gtk3 (gir bindings). - - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - - Notes - ----- - This methods sets the PyOS_InputHook for Gtk3, which allows - the Gtk3 to integrate with terminal based applications like - IPython. - """ - warn("This function is deprecated since IPython 5.0 and will be removed in future versions.", - DeprecationWarning, stacklevel=2) - from IPython.lib.inputhookgtk3 import inputhook_gtk3 - self.manager.set_inputhook(inputhook_gtk3) - - -clear_inputhook = inputhook_manager.clear_inputhook -set_inputhook = inputhook_manager.set_inputhook -current_gui = inputhook_manager.current_gui -clear_app_refs = inputhook_manager.clear_app_refs -enable_gui = inputhook_manager.enable_gui -disable_gui = inputhook_manager.disable_gui -register = inputhook_manager.register -guis = inputhook_manager.guihooks - - -def _deprecated_disable(): - warn("This function is deprecated since IPython 4.0 use disable_gui() instead", - DeprecationWarning, stacklevel=2) - inputhook_manager.disable_gui() - -disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \ - disable_pyglet = disable_osx = _deprecated_disable diff --git a/IPython/lib/inputhookglut.py b/IPython/lib/inputhookglut.py deleted file mode 100644 index e866ebdb82c..00000000000 --- a/IPython/lib/inputhookglut.py +++ /dev/null @@ -1,172 +0,0 @@ -# coding: utf-8 -""" -GLUT Inputhook support functions -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -# GLUT is quite an old library and it is difficult to ensure proper -# integration within IPython since original GLUT does not allow to handle -# events one by one. Instead, it requires for the mainloop to be entered -# and never returned (there is not even a function to exit he -# mainloop). Fortunately, there are alternatives such as freeglut -# (available for linux and windows) and the OSX implementation gives -# access to a glutCheckLoop() function that blocks itself until a new -# event is received. This means we have to setup the idle callback to -# ensure we got at least one event that will unblock the function. -# -# Furthermore, it is not possible to install these handlers without a window -# being first created. We choose to make this window invisible. This means that -# display mode options are set at this level and user won't be able to change -# them later without modifying the code. This should probably be made available -# via IPython options system. - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -import os -import sys -import time -import signal -import OpenGL.GLUT as glut -import OpenGL.platform as platform -from timeit import default_timer as clock - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - -# Frame per second : 60 -# Should probably be an IPython option -glut_fps = 60 - - -# Display mode : double buffeed + rgba + depth -# Should probably be an IPython option -glut_display_mode = (glut.GLUT_DOUBLE | - glut.GLUT_RGBA | - glut.GLUT_DEPTH) - -glutMainLoopEvent = None -if sys.platform == 'darwin': - try: - glutCheckLoop = platform.createBaseFunction( - 'glutCheckLoop', dll=platform.GLUT, resultType=None, - argTypes=[], - doc='glutCheckLoop( ) -> None', - argNames=(), - ) - except AttributeError as e: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions. ''' - '''Consider installing freeglut.''') from e - glutMainLoopEvent = glutCheckLoop -elif glut.HAVE_FREEGLUT: - glutMainLoopEvent = glut.glutMainLoopEvent -else: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions. ''' - '''Consider installing freeglut.''') - - -#----------------------------------------------------------------------------- -# Platform-dependent imports and functions -#----------------------------------------------------------------------------- - -if os.name == 'posix': - import select - - def stdin_ready(): - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - if infds: - return True - else: - return False - -elif sys.platform == 'win32': - import msvcrt - - def stdin_ready(): - return msvcrt.kbhit() - -#----------------------------------------------------------------------------- -# Callback functions -#----------------------------------------------------------------------------- - -def glut_display(): - # Dummy display function - pass - -def glut_idle(): - # Dummy idle function - pass - -def glut_close(): - # Close function only hides the current window - glut.glutHideWindow() - glutMainLoopEvent() - -def glut_int_handler(signum, frame): - # Catch sigint and print the default message - signal.signal(signal.SIGINT, signal.default_int_handler) - print('\nKeyboardInterrupt') - # Need to reprint the prompt at this stage - - - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- -def inputhook_glut(): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - - signal.signal(signal.SIGINT, glut_int_handler) - - try: - t = clock() - - # Make sure the default window is set after a window has been closed - if glut.glutGetWindow() == 0: - glut.glutSetWindow( 1 ) - glutMainLoopEvent() - return 0 - - while not stdin_ready(): - glutMainLoopEvent() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass - return 0 diff --git a/IPython/lib/inputhookgtk.py b/IPython/lib/inputhookgtk.py deleted file mode 100644 index 98569f54d75..00000000000 --- a/IPython/lib/inputhookgtk.py +++ /dev/null @@ -1,35 +0,0 @@ -# encoding: utf-8 -""" -Enable pygtk to be used interactively by setting PyOS_InputHook. - -Authors: Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -import gtk, gobject - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - - -def _main_quit(*args, **kwargs): - gtk.main_quit() - return False - -def inputhook_gtk(): - gobject.io_add_watch(sys.stdin, gobject.IO_IN, _main_quit) - gtk.main() - return 0 - diff --git a/IPython/lib/inputhookgtk3.py b/IPython/lib/inputhookgtk3.py deleted file mode 100644 index b797e862558..00000000000 --- a/IPython/lib/inputhookgtk3.py +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 -""" -Enable Gtk3 to be used interactively by IPython. - -Authors: Thomi Richards -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2012, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -from gi.repository import Gtk, GLib - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def _main_quit(*args, **kwargs): - Gtk.main_quit() - return False - - -def inputhook_gtk3(): - GLib.io_add_watch(sys.stdin, GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) - Gtk.main() - return 0 diff --git a/IPython/lib/inputhookgtk4.py b/IPython/lib/inputhookgtk4.py deleted file mode 100644 index a872cee36a0..00000000000 --- a/IPython/lib/inputhookgtk4.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Enable Gtk4 to be used interactively by IPython. -""" -# ----------------------------------------------------------------------------- -# Copyright (c) 2021, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -# ----------------------------------------------------------------------------- - -# ----------------------------------------------------------------------------- -# Imports -# ----------------------------------------------------------------------------- - -import sys - -from gi.repository import GLib - -# ----------------------------------------------------------------------------- -# Code -# ----------------------------------------------------------------------------- - - -class _InputHook: - def __init__(self, context): - self._quit = False - GLib.io_add_watch(sys.stdin, GLib.PRIORITY_DEFAULT, GLib.IO_IN, self.quit) - - def quit(self, *args, **kwargs): - self._quit = True - return False - - def run(self): - context = GLib.MainContext.default() - while not self._quit: - context.iteration(True) - - -def inputhook_gtk4(): - hook = _InputHook() - hook.run() - return 0 diff --git a/IPython/lib/inputhookpyglet.py b/IPython/lib/inputhookpyglet.py deleted file mode 100644 index fb91ffed177..00000000000 --- a/IPython/lib/inputhookpyglet.py +++ /dev/null @@ -1,111 +0,0 @@ -# encoding: utf-8 -""" -Enable pyglet to be used interactively by setting PyOS_InputHook. - -Authors -------- - -* Nicolas P. Rougier -* Fernando Perez -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import sys -import time -from timeit import default_timer as clock -import pyglet - -#----------------------------------------------------------------------------- -# Platform-dependent imports and functions -#----------------------------------------------------------------------------- - -if os.name == 'posix': - import select - - def stdin_ready(): - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - if infds: - return True - else: - return False - -elif sys.platform == 'win32': - import msvcrt - - def stdin_ready(): - return msvcrt.kbhit() - - -# On linux only, window.flip() has a bug that causes an AttributeError on -# window close. For details, see: -# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e - -if sys.platform.startswith('linux'): - def flip(window): - try: - window.flip() - except AttributeError: - pass -else: - def flip(window): - window.flip() - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def inputhook_pyglet(): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - try: - t = clock() - while not stdin_ready(): - pyglet.clock.tick() - for window in pyglet.app.windows: - window.switch_to() - window.dispatch_events() - window.dispatch_event('on_draw') - flip(window) - - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass - return 0 diff --git a/IPython/lib/inputhookqt4.py b/IPython/lib/inputhookqt4.py deleted file mode 100644 index 8a83902fc0e..00000000000 --- a/IPython/lib/inputhookqt4.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Qt4's inputhook support function - -Author: Christian Boos -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import os -import signal -import threading - -from IPython.core.interactiveshell import InteractiveShell -from IPython.external.qt_for_kernel import QtCore, QtGui -from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready - -#----------------------------------------------------------------------------- -# Module Globals -#----------------------------------------------------------------------------- - -got_kbdint = False -sigint_timer = None - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def create_inputhook_qt4(mgr, app=None): - """Create an input hook for running the Qt4 application event loop. - - Parameters - ---------- - mgr : an InputHookManager - - app : Qt Application, optional. - Running application to use. If not given, we probe Qt for an - existing application object, and create a new one if none is found. - - Returns - ------- - A pair consisting of a Qt Application (either the one given or the - one found or created) and a inputhook. - - Notes - ----- - We use a custom input hook instead of PyQt4's default one, as it - interacts better with the readline packages (issue #481). - - The inputhook function works in tandem with a 'pre_prompt_hook' - which automatically restores the hook as an inputhook in case the - latter has been temporarily disabled after having intercepted a - KeyboardInterrupt. - """ - - if app is None: - app = QtCore.QCoreApplication.instance() - if app is None: - app = QtGui.QApplication([" "]) - - # Re-use previously created inputhook if any - ip = InteractiveShell.instance() - if hasattr(ip, '_inputhook_qt4'): - return app, ip._inputhook_qt4 - - # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of - # hooks (they both share the got_kbdint flag) - - def inputhook_qt4(): - """PyOS_InputHook python hook for Qt4. - - Process pending Qt events and if there's no pending keyboard - input, spend a short slice of time (50ms) running the Qt event - loop. - - As a Python ctypes callback can't raise an exception, we catch - the KeyboardInterrupt and temporarily deactivate the hook, - which will let a *second* CTRL+C be processed normally and go - back to a clean prompt line. - """ - try: - allow_CTRL_C() - app = QtCore.QCoreApplication.instance() - if not app: # shouldn't happen, but safer if it happens anyway... - return 0 - app.processEvents(QtCore.QEventLoop.AllEvents, 300) - if not stdin_ready(): - # Generally a program would run QCoreApplication::exec() - # from main() to enter and process the Qt event loop until - # quit() or exit() is called and the program terminates. - # - # For our input hook integration, we need to repeatedly - # enter and process the Qt event loop for only a short - # amount of time (say 50ms) to ensure that Python stays - # responsive to other user inputs. - # - # A naive approach would be to repeatedly call - # QCoreApplication::exec(), using a timer to quit after a - # short amount of time. Unfortunately, QCoreApplication - # emits an aboutToQuit signal before stopping, which has - # the undesirable effect of closing all modal windows. - # - # To work around this problem, we instead create a - # QEventLoop and call QEventLoop::exec(). Other than - # setting some state variables which do not seem to be - # used anywhere, the only thing QCoreApplication adds is - # the aboutToQuit signal which is precisely what we are - # trying to avoid. - timer = QtCore.QTimer() - event_loop = QtCore.QEventLoop() - timer.timeout.connect(event_loop.quit) - while not stdin_ready(): - timer.start(50) - event_loop.exec_() - timer.stop() - except KeyboardInterrupt: - global got_kbdint, sigint_timer - - ignore_CTRL_C() - got_kbdint = True - mgr.clear_inputhook() - - # This generates a second SIGINT so the user doesn't have to - # press CTRL+C twice to get a clean prompt. - # - # Since we can't catch the resulting KeyboardInterrupt here - # (because this is a ctypes callback), we use a timer to - # generate the SIGINT after we leave this callback. - # - # Unfortunately this doesn't work on Windows (SIGINT kills - # Python and CTRL_C_EVENT doesn't work). - if(os.name == 'posix'): - pid = os.getpid() - if(not sigint_timer): - sigint_timer = threading.Timer(.01, os.kill, - args=[pid, signal.SIGINT] ) - sigint_timer.start() - else: - print("\nKeyboardInterrupt - Ctrl-C again for new prompt") - - - except: # NO exceptions are allowed to escape from a ctypes callback - ignore_CTRL_C() - from traceback import print_exc - print_exc() - print("Got exception from inputhook_qt4, unregistering.") - mgr.clear_inputhook() - finally: - allow_CTRL_C() - return 0 - - def preprompthook_qt4(ishell): - """'pre_prompt_hook' used to restore the Qt4 input hook - - (in case the latter was temporarily deactivated after a - CTRL+C) - """ - global got_kbdint, sigint_timer - - if(sigint_timer): - sigint_timer.cancel() - sigint_timer = None - - if got_kbdint: - mgr.set_inputhook(inputhook_qt4) - got_kbdint = False - - ip._inputhook_qt4 = inputhook_qt4 - ip.set_hook('pre_prompt_hook', preprompthook_qt4) - - return app, inputhook_qt4 diff --git a/IPython/lib/inputhookwx.py b/IPython/lib/inputhookwx.py deleted file mode 100644 index 60520a299c3..00000000000 --- a/IPython/lib/inputhookwx.py +++ /dev/null @@ -1,167 +0,0 @@ -# encoding: utf-8 - -""" -Enable wxPython to be used interactively by setting PyOS_InputHook. - -Authors: Robin Dunn, Brian Granger, Ondrej Certik -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys -import signal -import time -from timeit import default_timer as clock -import wx - -from IPython.lib.inputhook import stdin_ready - - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def inputhook_wx1(): - """Run the wx event loop by processing pending events only. - - This approach seems to work, but its performance is not great as it - relies on having PyOS_InputHook called regularly. - """ - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # Make a temporary event loop and process system events until - # there are no more waiting, then allow idle events (which - # will also deal with pending or posted wx events.) - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - while evtloop.Pending(): - evtloop.Dispatch() - app.ProcessIdle() - del ea - except KeyboardInterrupt: - pass - return 0 - -class EventLoopTimer(wx.Timer): - - def __init__(self, func): - self.func = func - wx.Timer.__init__(self) - - def Notify(self): - self.func() - -class EventLoopRunner(object): - - def Run(self, time): - self.evtloop = wx.EventLoop() - self.timer = EventLoopTimer(self.check_stdin) - self.timer.Start(time) - self.evtloop.Run() - - def check_stdin(self): - if stdin_ready(): - self.timer.Stop() - self.evtloop.Exit() - -def inputhook_wx2(): - """Run the wx event loop, polling for stdin. - - This version runs the wx eventloop for an undetermined amount of time, - during which it periodically checks to see if anything is ready on - stdin. If anything is ready on stdin, the event loop exits. - - The argument to elr.Run controls how often the event loop looks at stdin. - This determines the responsiveness at the keyboard. A setting of 1000 - enables a user to type at most 1 char per second. I have found that a - setting of 10 gives good keyboard response. We can shorten it further, - but eventually performance would suffer from calling select/kbhit too - often. - """ - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - elr = EventLoopRunner() - # As this time is made shorter, keyboard response improves, but idle - # CPU load goes up. 10 ms seems like a good compromise. - elr.Run(time=10) # CHANGE time here to control polling interval - except KeyboardInterrupt: - pass - return 0 - -def inputhook_wx3(): - """Run the wx event loop by processing pending events only. - - This is like inputhook_wx1, but it keeps processing pending events - until stdin is ready. After processing all pending events, a call to - time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. - This sleep time should be tuned though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - try: - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # The import of wx on Linux sets the handler for signal.SIGINT - # to 0. This is a bug in wx or gtk. We fix by just setting it - # back to the Python default. - if not callable(signal.getsignal(signal.SIGINT)): - signal.signal(signal.SIGINT, signal.default_int_handler) - - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - t = clock() - while not stdin_ready(): - while evtloop.Pending(): - t = clock() - evtloop.Dispatch() - app.ProcessIdle() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - del ea - except KeyboardInterrupt: - pass - return 0 - -if sys.platform == 'darwin': - # On OSX, evtloop.Pending() always returns True, regardless of there being - # any events pending. As such we can't use implementations 1 or 3 of the - # inputhook as those depend on a pending/dispatch loop. - inputhook_wx = inputhook_wx2 -else: - # This is our default implementation - inputhook_wx = inputhook_wx3 diff --git a/codecov.yml b/codecov.yml index 2d3b8bb058c..c41a76c4cb2 100644 --- a/codecov.yml +++ b/codecov.yml @@ -17,7 +17,6 @@ ignore: - IPython/kernel/* - IPython/consoleapp.py - IPython/core/inputsplitter.py - - IPython/lib/inputhook*.py - IPython/lib/kernel.py - IPython/utils/jsonutil.py - IPython/utils/localinterfaces.py diff --git a/examples/IPython Kernel/gui/gui-gtk.py b/examples/IPython Kernel/gui/gui-gtk.py deleted file mode 100755 index 3df9922b34b..00000000000 --- a/examples/IPython Kernel/gui/gui-gtk.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -"""Simple GTK example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui gtk - -In [6]: %run gui-gtk.py -""" - -import pygtk -pygtk.require('2.0') -import gtk - - -def hello_world(widget, data=None): - print("Hello World") - -def delete_event(widget, event, data=None): - return False - -def destroy(widget, data=None): - gtk.main_quit() - -window = gtk.Window(gtk.WINDOW_TOPLEVEL) -window.connect("delete_event", delete_event) -window.connect("destroy", destroy) -button = gtk.Button("Hello World") -button.connect("clicked", hello_world, None) - -window.add(button) -button.show() -window.show() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('gtk') -except ImportError: - gtk.main() diff --git a/examples/IPython Kernel/gui/gui-gtk3.py b/examples/IPython Kernel/gui/gui-gtk3.py deleted file mode 100644 index f35f498d33b..00000000000 --- a/examples/IPython Kernel/gui/gui-gtk3.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -"""Simple Gtk example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [1]: %gui gtk3 - -In [2]: %run gui-gtk3.py -""" - -from gi.repository import Gtk - - -def hello_world(widget, data=None): - print("Hello World") - -def delete_event(widget, event, data=None): - return False - -def destroy(widget, data=None): - Gtk.main_quit() - -window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) -window.connect("delete_event", delete_event) -window.connect("destroy", destroy) -button = Gtk.Button(label="Hello World") -button.connect("clicked", hello_world, None) - -window.add(button) -button.show() -window.show() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('gtk3') -except ImportError: - Gtk.main() diff --git a/examples/IPython Kernel/gui/gui-pyglet.py b/examples/IPython Kernel/gui/gui-pyglet.py deleted file mode 100644 index f64162286a0..00000000000 --- a/examples/IPython Kernel/gui/gui-pyglet.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -"""Simple pyglet example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui pyglet - -In [6]: %run gui-pyglet.py -""" - -import pyglet - - -window = pyglet.window.Window() -label = pyglet.text.Label('Hello, world', - font_name='Times New Roman', - font_size=36, - x=window.width//2, y=window.height//2, - anchor_x='center', anchor_y='center') -@window.event -def on_close(): - window.close() - -@window.event -def on_draw(): - window.clear() - label.draw() - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('pyglet') -except ImportError: - pyglet.app.run() diff --git a/examples/IPython Kernel/gui/gui-tk.py b/examples/IPython Kernel/gui/gui-tk.py deleted file mode 100755 index 89caf3ed622..00000000000 --- a/examples/IPython Kernel/gui/gui-tk.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -"""Simple Tk example to manually test event loop integration. - -This is meant to run tests manually in ipython as: - -In [5]: %gui tk - -In [6]: %run gui-tk.py -""" - -from tkinter import * - - -class MyApp: - - def __init__(self, root): - frame = Frame(root) - frame.pack() - - self.button = Button(frame, text="Hello", command=self.hello_world) - self.button.pack(side=LEFT) - - def hello_world(self): - print("Hello World!") - -root = Tk() - -app = MyApp(root) - -try: - from IPython.lib.inputhook import enable_gui - enable_gui('tk', root) -except ImportError: - root.mainloop() diff --git a/examples/IPython Kernel/gui/gui-wx.py b/examples/IPython Kernel/gui/gui-wx.py deleted file mode 100755 index 86e37acfc9d..00000000000 --- a/examples/IPython Kernel/gui/gui-wx.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -""" -A Simple wx example to test IPython's event loop integration. - -To run this do: - -In [5]: %gui wx # or start IPython with '--gui wx' - -In [6]: %run gui-wx.py - -Ref: Modified from wxPython source code wxPython/samples/simple/simple.py -""" - -import wx - - -class MyFrame(wx.Frame): - """ - This is MyFrame. It just shows a few controls on a wxPanel, - and has a simple menu. - """ - def __init__(self, parent, title): - wx.Frame.__init__(self, parent, -1, title, - pos=(150, 150), size=(350, 200)) - - # Create the menubar - menuBar = wx.MenuBar() - - # and a menu - menu = wx.Menu() - - # add an item to the menu, using \tKeyName automatically - # creates an accelerator, the third param is some help text - # that will show up in the statusbar - menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") - - # bind the menu event to an event handler - self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT) - - # and put the menu on the menubar - menuBar.Append(menu, "&File") - self.SetMenuBar(menuBar) - - self.CreateStatusBar() - - # Now create the Panel to put the other controls on. - panel = wx.Panel(self) - - # and a few controls - text = wx.StaticText(panel, -1, "Hello World!") - text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) - text.SetSize(text.GetBestSize()) - btn = wx.Button(panel, -1, "Close") - funbtn = wx.Button(panel, -1, "Just for fun...") - - # bind the button events to handlers - self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn) - self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn) - - # Use a sizer to layout the controls, stacked vertically and with - # a 10 pixel border around each - sizer = wx.BoxSizer(wx.VERTICAL) - sizer.Add(text, 0, wx.ALL, 10) - sizer.Add(btn, 0, wx.ALL, 10) - sizer.Add(funbtn, 0, wx.ALL, 10) - panel.SetSizer(sizer) - panel.Layout() - - - def OnTimeToClose(self, evt): - """Event handler for the button click.""" - print("See ya later!") - self.Close() - - def OnFunButton(self, evt): - """Event handler for the button click.""" - print("Having fun yet?") - - -class MyApp(wx.App): - def OnInit(self): - frame = MyFrame(None, "Simple wxPython App") - self.SetTopWindow(frame) - - print("Print statements go to this stdout window by default.") - - frame.Show(True) - return True - - -if __name__ == '__main__': - - app = wx.GetApp() - if app is None: - app = MyApp(redirect=False, clearSigInt=False) - else: - frame = MyFrame(None, "Simple wxPython App") - app.SetTopWindow(frame) - print("Print statements go to this stdout window by default.") - frame.Show(True) - - try: - from IPython.lib.inputhook import enable_gui - enable_gui('wx', app) - except ImportError: - app.MainLoop() diff --git a/pytest.ini b/pytest.ini index 9189cf6c33b..e0cfec07bdf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -36,7 +36,6 @@ addopts = --durations=10 --ignore=IPython/kernel --ignore=IPython/consoleapp.py --ignore=IPython/core/inputsplitter.py - --ignore-glob=IPython/lib/inputhook*.py --ignore=IPython/lib/kernel.py --ignore=IPython/utils/jsonutil.py --ignore=IPython/utils/localinterfaces.py From da495d08e6a46cf09ead0d69f1658f516045ac77 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 15:02:47 -0800 Subject: [PATCH 1773/3726] More Deprecation Removal This remove a bunch or IO objects that have been deprecated for at least 2 versions. I expect this to maybe create issues on windows, and maybe ipykernel because of colorama. --- IPython/core/interactiveshell.py | 13 +---- IPython/terminal/interactiveshell.py | 10 ---- IPython/testing/globalipapp.py | 24 -------- IPython/utils/io.py | 85 ---------------------------- IPython/utils/tests/test_io.py | 28 +-------- 5 files changed, 4 insertions(+), 156 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index b48b572ad17..519494118ba 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -712,16 +712,9 @@ def init_inspector(self, changes=None): self.object_info_string_level) def init_io(self): - # This will just use sys.stdout and sys.stderr. If you want to - # override sys.stdout and sys.stderr themselves, you need to do that - # *before* instantiating this class, because io holds onto - # references to the underlying streams. - # io.std* are deprecated, but don't show our own deprecation warnings - # during initialization of the deprecated API. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - io.stdout = io.IOStream(sys.stdout) - io.stderr = io.IOStream(sys.stderr) + # implemented in subclasses, TerminalInteractiveShell does call + # colorama.init(). + pass def init_prompts(self): # Set system prompts, so that scripts can decide if they are running diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 5e7d95fcfc0..c4fa47911fe 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -540,16 +540,6 @@ def init_io(self): import colorama colorama.init() - # For some reason we make these wrappers around stdout/stderr. - # For now, we need to reset them so all output gets coloured. - # https://github.com/ipython/ipython/issues/8669 - # io.std* are deprecated, but don't show our own deprecation warnings - # during initialization of the deprecated API. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - io.stdout = io.IOStream(sys.stdout) - io.stderr = io.IOStream(sys.stderr) - def init_magics(self): super(TerminalInteractiveShell, self).init_magics() self.register_magics(TerminalMagics) diff --git a/IPython/testing/globalipapp.py b/IPython/testing/globalipapp.py index c58d575a73b..698e3d845aa 100644 --- a/IPython/testing/globalipapp.py +++ b/IPython/testing/globalipapp.py @@ -23,30 +23,6 @@ from IPython.terminal.interactiveshell import TerminalInteractiveShell -class StreamProxy(io.IOStream): - """Proxy for sys.stdout/err. This will request the stream *at call time* - allowing for nose's Capture plugin's redirection of sys.stdout/err. - - Parameters - ---------- - name : str - The name of the stream. This will be requested anew at every call - """ - - def __init__(self, name): - warnings.warn("StreamProxy is deprecated and unused as of IPython 5", DeprecationWarning, - stacklevel=2, - ) - self.name=name - - @property - def stream(self): - return getattr(sys, self.name) - - def flush(self): - self.stream.flush() - - def get_ipython(): # This will get replaced by the real thing once we start IPython below return start_ipython() diff --git a/IPython/utils/io.py b/IPython/utils/io.py index 638471e8fbb..69e4d4e0cdb 100644 --- a/IPython/utils/io.py +++ b/IPython/utils/io.py @@ -19,82 +19,10 @@ from IPython.utils.decorators import undoc from .capture import CapturedIO, capture_output -@undoc -class IOStream: - - def __init__(self, stream, fallback=None): - warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead', - DeprecationWarning, stacklevel=2) - if not hasattr(stream,'write') or not hasattr(stream,'flush'): - if fallback is not None: - stream = fallback - else: - raise ValueError("fallback required, but not specified") - self.stream = stream - self._swrite = stream.write - - # clone all methods not overridden: - def clone(meth): - return not hasattr(self, meth) and not meth.startswith('_') - for meth in filter(clone, dir(stream)): - try: - val = getattr(stream, meth) - except AttributeError: - pass - else: - setattr(self, meth, val) - - def __repr__(self): - cls = self.__class__ - tpl = '{mod}.{cls}({args})' - return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream) - - def write(self,data): - warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead', - DeprecationWarning, stacklevel=2) - try: - self._swrite(data) - except: - try: - # print handles some unicode issues which may trip a plain - # write() call. Emulate write() by using an empty end - # argument. - print(data, end='', file=self.stream) - except: - # if we get here, something is seriously broken. - print('ERROR - failed to write data to stream:', self.stream, - file=sys.stderr) - - def writelines(self, lines): - warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead', - DeprecationWarning, stacklevel=2) - if isinstance(lines, str): - lines = [lines] - for line in lines: - self.write(line) - - # This class used to have a writeln method, but regular files and streams - # in Python don't have this method. We need to keep this completely - # compatible so we removed it. - - @property - def closed(self): - return self.stream.closed - - def close(self): - pass - # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr devnull = open(os.devnull, 'w') atexit.register(devnull.close) -# io.std* are deprecated, but don't show our own deprecation warnings -# during initialization of the deprecated API. -with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - stdin = IOStream(sys.stdin, fallback=devnull) - stdout = IOStream(sys.stdout, fallback=devnull) - stderr = IOStream(sys.stderr, fallback=devnull) class Tee(object): """A class to duplicate an output stream to stdout/err. @@ -208,12 +136,6 @@ def temp_pyfile(src, ext='.py'): f.flush() return fname -@undoc -def atomic_writing(*args, **kwargs): - """DEPRECATED: moved to notebook.services.contents.fileio""" - warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2) - from notebook.services.contents.fileio import atomic_writing - return atomic_writing(*args, **kwargs) @undoc def raw_print(*args, **kw): @@ -232,10 +154,3 @@ def raw_print_err(*args, **kw): print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'), file=sys.__stderr__) sys.__stderr__.flush() - -@undoc -def unicode_std_stream(stream='stdout'): - """DEPRECATED, moved to nbconvert.utils.io""" - warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2) - from nbconvert.utils.io import unicode_std_stream - return unicode_std_stream(stream) diff --git a/IPython/utils/tests/test_io.py b/IPython/utils/tests/test_io.py index fa3abab748b..3b4c03eae06 100644 --- a/IPython/utils/tests/test_io.py +++ b/IPython/utils/tests/test_io.py @@ -11,7 +11,7 @@ from subprocess import Popen, PIPE import unittest -from IPython.utils.io import IOStream, Tee, capture_output +from IPython.utils.io import Tee, capture_output def test_tee_simple(): @@ -48,34 +48,8 @@ def test(self): for chan in ['stdout', 'stderr']: self.tchan(chan) -def test_io_init(): - """Test that io.stdin/out/err exist at startup""" - for name in ('stdin', 'stdout', 'stderr'): - cmd = "from IPython.utils import io;print(io.%s.__class__)"%name - with Popen([sys.executable, '-c', cmd], stdout=PIPE) as p: - p.wait() - classname = p.stdout.read().strip().decode('ascii') - # __class__ is a reference to the class object in Python 3, so we can't - # just test for string equality. - assert 'IPython.utils.io.IOStream' in classname, classname - class TestIOStream(unittest.TestCase): - def test_IOStream_init(self): - """IOStream initializes from a file-like object missing attributes. """ - # Cause a failure from getattr and dir(). (Issue #6386) - class BadStringIO(StringIO): - def __dir__(self): - attrs = super().__dir__() - attrs.append('name') - return attrs - with self.assertWarns(DeprecationWarning): - iostream = IOStream(BadStringIO()) - iostream.write('hi, bad iostream\n') - - assert not hasattr(iostream, 'name') - iostream.close() - def test_capture_output(self): """capture_output() context works""" From 8cc2c16d4ee18df19beeb43822ebc072462fbeb2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:02:32 -0800 Subject: [PATCH 1774/3726] Remove deprecated IPython.nbformat Deprecated since IPython 4.0, which predates Python 3.6 --- IPython/nbformat.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 IPython/nbformat.py diff --git a/IPython/nbformat.py b/IPython/nbformat.py deleted file mode 100644 index 310277de009..00000000000 --- a/IPython/nbformat.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.nbformat imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.nbformat` package has been deprecated since IPython 4.0. " - "You should import from nbformat instead.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.nbformat'] = ShimModule( - src='IPython.nbformat', mirror='nbformat') From 1d6d1ab9d61ba720af8606e478951f5f4e8e8cde Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:04:13 -0800 Subject: [PATCH 1775/3726] Remove deprecated submodule IPython.nbconvert. This has been deprecated since IPython 4.0, pre python 3.6 --- IPython/nbconvert.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 IPython/nbconvert.py diff --git a/IPython/nbconvert.py b/IPython/nbconvert.py deleted file mode 100644 index 2de4ee50bc7..00000000000 --- a/IPython/nbconvert.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.nbconvert imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.nbconvert` package has been deprecated since IPython 4.0. " - "You should import from nbconvert instead.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.nbconvert'] = ShimModule( - src='IPython.nbconvert', mirror='nbconvert') From 2c1125bc49ade78ab9d0791c50d1762bab03ce13 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 15:59:50 -0800 Subject: [PATCH 1776/3726] Remove shimmodule IPython.config Deprecated since IPython 4.0 This should not be relied upon as this was deprecated Python 3.6 was not released yet. --- IPython/config.py | 19 ------------------- IPython/utils/tests/test_shimmodule.py | 14 -------------- 2 files changed, 33 deletions(-) delete mode 100644 IPython/config.py delete mode 100644 IPython/utils/tests/test_shimmodule.py diff --git a/IPython/config.py b/IPython/config.py deleted file mode 100644 index 964f46f10ac..00000000000 --- a/IPython/config.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.config imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from .utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.config` package has been deprecated since IPython 4.0. " - "You should import from traitlets.config instead.", ShimWarning) - - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.config'] = ShimModule(src='IPython.config', mirror='traitlets.config') diff --git a/IPython/utils/tests/test_shimmodule.py b/IPython/utils/tests/test_shimmodule.py deleted file mode 100644 index 30f2ffa09f2..00000000000 --- a/IPython/utils/tests/test_shimmodule.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest -import sys - -from IPython.utils.shimmodule import ShimWarning - - -def test_shim_warning(): - sys.modules.pop('IPython.config', None) - with pytest.warns(ShimWarning): - import IPython.config - - import traitlets.config - - assert IPython.config.Config is traitlets.config.Config From d0f56447845fa74cc38ed656677645c8890df0f5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:15:19 -0800 Subject: [PATCH 1777/3726] Increase Codecov threshold for test. It is currently too sensitive. --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 2d3b8bb058c..7054b5b2979 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,6 +10,7 @@ coverage: tests: target: auto paths: ['.*/tests/.*'] + threshold: 0.1% codecov: require_ci_to_pass: false From 9e1981ea54b387d05122fd604a021fd237a195c0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:13:26 -0800 Subject: [PATCH 1778/3726] Remove deprecated banner parameter All of that was deprecated at or before 5.0 --- IPython/core/completer.py | 2 +- IPython/core/magics/config.py | 2 +- IPython/terminal/interactiveshell.py | 11 +- IPython/terminal/ptshell.py | 8 -- tools/backport_pr.py | 192 --------------------------- 5 files changed, 4 insertions(+), 211 deletions(-) delete mode 100644 IPython/terminal/ptshell.py delete mode 100755 tools/backport_pr.py diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 356a1e56568..7d5d9db0a6d 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -591,7 +591,7 @@ class Completer(Configurable): greedy = Bool(False, help="""Activate greedy completion - PENDING DEPRECTION. this is now mostly taken care of with Jedi. + PENDING DEPRECATION. 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. diff --git a/IPython/core/magics/config.py b/IPython/core/magics/config.py index 25a74e041ab..c1387b601b8 100644 --- a/IPython/core/magics/config.py +++ b/IPython/core/magics/config.py @@ -82,7 +82,7 @@ def config(self, s): Current: False IPCompleter.greedy= Activate greedy completion - PENDING DEPRECTION. this is now mostly taken care of with Jedi. + PENDING DEPRECATION. 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 diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 5e7d95fcfc0..c886b4be728 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -51,7 +51,6 @@ from .ptutils import IPythonPTCompleter, IPythonPTLexer from .shortcuts import create_ipython_shortcuts -DISPLAY_BANNER_DEPRECATED = object() PTK3 = ptk_version.startswith('3.') @@ -579,11 +578,7 @@ def ask_exit(self): rl_next_input = None - def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): - - if display_banner is not DISPLAY_BANNER_DEPRECATED: - warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) - + def interact(self): self.keep_running = True while self.keep_running: print(self.separate_in, end='') @@ -599,11 +594,9 @@ def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): if code: self.run_cell(code, store_history=True) - def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): + def mainloop(self): # An extra layer of protection in case someone mashing Ctrl-C breaks # out of our internal code. - if display_banner is not DISPLAY_BANNER_DEPRECATED: - warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) while True: try: self.interact() diff --git a/IPython/terminal/ptshell.py b/IPython/terminal/ptshell.py deleted file mode 100644 index 666d3c5b514..00000000000 --- a/IPython/terminal/ptshell.py +++ /dev/null @@ -1,8 +0,0 @@ -raise DeprecationWarning("""DEPRECATED: - -After Popular request and decision from the BDFL: -`IPython.terminal.ptshell` has been moved back to `IPython.terminal.interactiveshell` -during the beta cycle (after IPython 5.0.beta3) Sorry about that. - -This file will be removed in 5.0 rc or final. -""") diff --git a/tools/backport_pr.py b/tools/backport_pr.py deleted file mode 100755 index c4c06b5526b..00000000000 --- a/tools/backport_pr.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python -""" -Backport pull requests to a particular branch. - -Usage: backport_pr.py [org/repository] branch [PR] [PR2] - -e.g.: - - python tools/backport_pr.py 0.13.1 123 155 - -to backport PRs #123 and #155 onto branch 0.13.1 - -or - - python tools/backport_pr.py 2.1 - -to see what PRs are marked for backport with milestone=2.1 that have yet to be applied -to branch 2.x - -or - - python tools/backport_pr.py jupyter/notebook 0.13.1 123 155 - -to backport PRs #123 and #155 of the `jupyter/notebook` repo onto branch 0.13.1 -of that repo. - -""" - - -import os -import re -import sys - -from subprocess import Popen, PIPE, check_call, check_output -try: - from urllib.request import urlopen -except: - from urllib import urlopen - -from gh_api import ( - get_issues_list, - get_pull_request, - get_pull_request_files, - is_pull_request, - get_milestone_id, -) - -def find_rejects(root='.'): - for dirname, dirs, files in os.walk(root): - for fname in files: - if fname.endswith('.rej'): - yield os.path.join(dirname, fname) - -def get_current_branch(): - branches = check_output(['git', 'branch']) - for branch in branches.splitlines(): - if branch.startswith(b'*'): - return branch[1:].strip().decode('utf-8') - -def backport_pr(branch, num, project='ipython/ipython'): - current_branch = get_current_branch() - if branch != current_branch: - check_call(['git', 'checkout', branch]) - check_call(['git', 'pull']) - pr = get_pull_request(project, num, auth=True) - files = get_pull_request_files(project, num, auth=True) - patch_url = pr['patch_url'] - title = pr['title'] - description = pr['body'] - fname = "PR%i.patch" % num - if os.path.exists(fname): - print("using patch from {fname}".format(**locals())) - with open(fname, 'rb') as f: - patch = f.read() - else: - req = urlopen(patch_url) - patch = req.read() - - lines = description.splitlines() - if len(lines) > 5: - lines = lines[:5] + ['...'] - description = '\n'.join(lines) - - msg = "Backport PR #%i: %s" % (num, title) + '\n\n' + description - check = Popen(['git', 'apply', '--check', '--verbose'], stdin=PIPE) - a,b = check.communicate(patch) - - if check.returncode: - print("patch did not apply, saving to {fname}".format(**locals())) - print("edit {fname} until `cat {fname} | git apply --check` succeeds".format(**locals())) - print("then run tools/backport_pr.py {num} again".format(**locals())) - if not os.path.exists(fname): - with open(fname, 'wb') as f: - f.write(patch) - return 1 - - p = Popen(['git', 'apply'], stdin=PIPE) - a,b = p.communicate(patch) - - filenames = [ f['filename'] for f in files ] - - check_call(['git', 'add'] + filenames) - - check_call(['git', 'commit', '-m', msg]) - - print("PR #%i applied, with msg:" % num) - print() - print(msg) - print() - - if branch != current_branch: - check_call(['git', 'checkout', current_branch]) - - return 0 - -backport_re = re.compile(r"(?:[Bb]ackport|[Mm]erge).*#(\d+)") - -def already_backported(branch, since_tag=None): - """return set of PRs that have been backported already""" - if since_tag is None: - since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip() - cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline'] - lines = check_output(cmd).decode('utf8') - return set(int(num) for num in backport_re.findall(lines)) - -def should_backport(labels=None, milestone=None, project='ipython/ipython'): - """return set of PRs marked for backport""" - if labels is None and milestone is None: - raise ValueError("Specify one of labels or milestone.") - elif labels is not None and milestone is not None: - raise ValueError("Specify only one of labels or milestone.") - if labels is not None: - issues = get_issues_list(project, - labels=labels, - state='closed', - auth=True, - ) - else: - milestone_id = get_milestone_id(project, milestone, - auth=True) - issues = get_issues_list(project, - milestone=milestone_id, - state='closed', - auth=True, - ) - - should_backport = set() - for issue in issues: - if not is_pull_request(issue): - continue - pr = get_pull_request(project, issue['number'], - auth=True) - if not pr['merged']: - print ("Marked PR closed without merge: %i" % pr['number']) - continue - if pr['base']['ref'] != 'master': - continue - should_backport.add(pr['number']) - return should_backport - -if __name__ == '__main__': - project = 'ipython/ipython' - - print("DEPRECATE: backport_pr.py is deprecated and it is now recommended" - "to install `ghpro` from PyPI.", file=sys.stderr) - - args = list(sys.argv) - if len(args) >= 2: - if '/' in args[1]: - project = args[1] - del args[1] - - if len(args) < 2: - print(__doc__) - sys.exit(1) - - if len(args) < 3: - milestone = args[1] - branch = milestone.split('.')[0] + '.x' - already = already_backported(branch) - should = should_backport(milestone=milestone, project=project) - print ("The following PRs should be backported:") - for pr in sorted(should.difference(already)): - print (pr) - sys.exit(0) - - for prno in map(int, args[2:]): - print("Backporting PR #%i" % prno) - rc = backport_pr(args[1], prno, project=project) - if rc: - print("Backporting PR #%i failed" % prno) - sys.exit(rc) From 5e798c81c0c1d62210f502bac70e931042a17b36 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:18:40 -0800 Subject: [PATCH 1779/3726] Put version next to Deprecation notice. So that when I grep through files I don't have to open them an search to know since when. --- IPython/core/oinspect.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 3f12d519d7a..70a64c3952e 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -221,7 +221,7 @@ def format_argspec(argspec): This takes a dict instead of ordered arguments and calls inspect.format_argspec with the arguments in the necessary order. - DEPRECATED: Do not use; will be removed in future versions. + DEPRECATED (since 7.10): Do not use; will be removed in future versions. """ warnings.warn('`format_argspec` function is deprecated as of IPython 7.10' @@ -233,10 +233,13 @@ def format_argspec(argspec): @undoc def call_tip(oinfo, format_call=True): - """DEPRECATED. Extract call tip data from an oinfo dict. - """ - warnings.warn('`call_tip` function is deprecated as of IPython 6.0' - 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) + """DEPRECATED since 6.0. Extract call tip data from an oinfo dict.""" + warnings.warn( + "`call_tip` function is deprecated as of IPython 6.0" + "and will be removed in future versions.", + DeprecationWarning, + stacklevel=2, + ) # Get call definition argspec = oinfo.get('argspec') if argspec is None: @@ -701,9 +704,8 @@ def pinfo( del info['text/html'] page.page(info) - def info(self, obj, oname='', formatter=None, info=None, detail_level=0): - """DEPRECATED. Compute a dict with detailed information about an object. - """ + def info(self, obj, oname="", formatter=None, info=None, detail_level=0): + """DEPRECATED since 5.0. Compute a dict with detailed information about an object.""" if formatter is not None: warnings.warn('The `formatter` keyword argument to `Inspector.info`' 'is deprecated as of IPython 5.0 and will have no effects.', From 7195786ea7af3975ff8c5225899d4d8c8c8329bc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:27:13 -0800 Subject: [PATCH 1780/3726] Remove more deprecated utilities. Most subcommands have been in jupyter for many years, and the -e export flags has been doing nothing for a long time as well. --- IPython/core/magics/basic.py | 7 ------- IPython/core/tests/test_magic.py | 2 +- IPython/terminal/ipapp.py | 26 +------------------------- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 4a8f223c206..b44b4a38128 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -560,10 +560,6 @@ def precision(self, s=''): return ptformatter.float_format @magic_arguments.magic_arguments() - @magic_arguments.argument( - '-e', '--export', action='store_true', default=False, - help=argparse.SUPPRESS - ) @magic_arguments.argument( 'filename', type=str, help='Notebook name or filename' @@ -574,9 +570,6 @@ def notebook(self, s): This function can export the current IPython history to a notebook file. For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb". - - The -e or --export flag is deprecated in IPython 5.2, and will be - removed in the future. """ args = magic_arguments.parse_argstring(self.notebook, s) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 641a6ff66a1..428d7114a35 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -750,7 +750,7 @@ def test_notebook_export_json(): _ip.history_manager.store_inputs(i, cmd) with TemporaryDirectory() as td: outfile = os.path.join(td, "nb.ipynb") - _ip.magic("notebook -e %s" % outfile) + _ip.magic("notebook %s" % outfile) class TestEnv(TestCase): diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py index defe3e79fa8..b95266eaef0 100755 --- a/IPython/terminal/ipapp.py +++ b/IPython/terminal/ipapp.py @@ -208,26 +208,6 @@ def _classes_default(self): StoreMagics, ] - deprecated_subcommands = dict( - qtconsole=('qtconsole.qtconsoleapp.JupyterQtConsoleApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter Qt Console.""" - ), - notebook=('notebook.notebookapp.NotebookApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter HTML Notebook Server.""" - ), - console=('jupyter_console.app.ZMQTerminalIPythonApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter terminal-based Console.""" - ), - nbconvert=('nbconvert.nbconvertapp.NbConvertApp', - "DEPRECATED, Will be removed in IPython 6.0 : Convert notebooks to/from other formats." - ), - trust=('nbformat.sign.TrustNotebookApp', - "DEPRECATED, Will be removed in IPython 6.0 : Sign notebooks to trust their potentially unsafe contents at load." - ), - kernelspec=('jupyter_client.kernelspecapp.KernelSpecApp', - "DEPRECATED, Will be removed in IPython 6.0 : Manage Jupyter kernel specifications." - ), - ) subcommands = dict( profile = ("IPython.core.profileapp.ProfileApp", "Create and manage IPython profiles." @@ -242,11 +222,7 @@ def _classes_default(self): "Manage the IPython history database." ), ) - deprecated_subcommands['install-nbextension'] = ( - "notebook.nbextensions.InstallNBExtensionApp", - "DEPRECATED, Will be removed in IPython 6.0 : Install Jupyter notebook extension files" - ) - subcommands.update(deprecated_subcommands) + # *do* autocreate requested profile, but don't create the config file. auto_create=Bool(True) From 8d544141fed373ffbc812b9a2fb796038d7a68ab Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 16:54:45 -0800 Subject: [PATCH 1781/3726] remove some dead code especially wrt py3compat --- IPython/core/completer.py | 19 +++------------- IPython/core/tests/test_iplib.py | 8 +++---- IPython/utils/_process_cli.py | 1 - IPython/utils/_process_posix.py | 1 - IPython/utils/py3compat.py | 37 ++++++++++---------------------- 5 files changed, 18 insertions(+), 48 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 7d5d9db0a6d..cc7b629e2e1 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -178,14 +178,6 @@ MATCHES_LIMIT = 500 -class Sentinel: - def __repr__(self): - return "" - - -_deprecation_readline_sentinel = Sentinel() - - class ProvisionalCompleterWarning(FutureWarning): """ Exception raise by an experimental feature in this module. @@ -618,8 +610,6 @@ class Completer(Configurable): "Includes completion of latex commands, unicode names, and expanding " "unicode characters back to latex commands.").tag(config=True) - - def __init__(self, namespace=None, global_namespace=None, **kwargs): """Create a new completer for the command line. @@ -1119,8 +1109,9 @@ def _limit_to_all_changed(self, change): 'no effects and then removed in future version of IPython.', UserWarning) - def __init__(self, shell=None, namespace=None, global_namespace=None, - use_readline=_deprecation_readline_sentinel, config=None, **kwargs): + def __init__( + self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs + ): """IPCompleter() -> completer Return a completer object. @@ -1144,10 +1135,6 @@ def __init__(self, shell=None, namespace=None, global_namespace=None, self.magic_escape = ESC_MAGIC self.splitter = CompletionSplitter() - if use_readline is not _deprecation_readline_sentinel: - warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', - DeprecationWarning, stacklevel=2) - # _greedy_changed() depends on splitter and readline being defined: Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, config=config, **kwargs) diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py index 9bdfd514644..7fa4bbae399 100644 --- a/IPython/core/tests/test_iplib.py +++ b/IPython/core/tests/test_iplib.py @@ -133,7 +133,7 @@ def doctest_tb_sysexit(): In [20]: %tb Traceback (most recent call last): File ..., in execfile - exec(compiler(f.read(), fname, 'exec'), glob, loc) + exec(compiler(f.read(), fname, "exec"), glob, loc) File ..., in bar(mode) File ..., in bar @@ -149,9 +149,9 @@ def doctest_tb_sysexit(): --------------------------------------------------------------------------- SystemExit Traceback (most recent call last) File ..., in execfile(fname, glob, loc, compiler) - 70 with open(fname, 'rb') as f: - 71 compiler = compiler or compile - ---> 72 exec(compiler(f.read(), fname, 'exec'), glob, loc) + ... with open(fname, "rb") as f: + ... compiler = compiler or compile + ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc) ... 30 except IndexError: 31 mode = 'div' diff --git a/IPython/utils/_process_cli.py b/IPython/utils/_process_cli.py index 03b2ecc7997..86e918a8d02 100644 --- a/IPython/utils/_process_cli.py +++ b/IPython/utils/_process_cli.py @@ -19,7 +19,6 @@ import os # Import IPython libraries: -from IPython.utils import py3compat from ._process_common import arg_split diff --git a/IPython/utils/_process_posix.py b/IPython/utils/_process_posix.py index 5f6b1ab200f..59b5c238960 100644 --- a/IPython/utils/_process_posix.py +++ b/IPython/utils/_process_posix.py @@ -24,7 +24,6 @@ # Our own from ._process_common import getoutput, arg_split -from IPython.utils import py3compat from IPython.utils.encoding import DEFAULT_ENCODING #----------------------------------------------------------------------------- diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py index 654195e6c84..34af4c58f42 100644 --- a/IPython/utils/py3compat.py +++ b/IPython/utils/py3compat.py @@ -3,13 +3,8 @@ This file is deprecated and will be removed in a future version. """ -import functools -import os -import sys -import re -import shutil -import types import platform +import builtins as builtin_mod from .encoding import DEFAULT_ENCODING @@ -18,6 +13,7 @@ def decode(s, encoding=None): encoding = encoding or DEFAULT_ENCODING return s.decode(encoding, "replace") + def encode(u, encoding=None): encoding = encoding or DEFAULT_ENCODING return u.encode(encoding, "replace") @@ -28,16 +24,6 @@ def cast_unicode(s, encoding=None): return decode(s, encoding) return s -def cast_bytes(s, encoding=None): - if not isinstance(s, bytes): - return encode(s, encoding) - return s - -def buffer_to_bytes(buf): - """Cast a buffer object to bytes""" - if not isinstance(buf, bytes): - buf = bytes(buf) - return buf def safe_unicode(e): """unicode(e) with various fallbacks. Used for exceptions, which may not be @@ -53,23 +39,21 @@ def safe_unicode(e): except UnicodeError: pass - return u'Unrecoverably corrupt evalue' + return "Unrecoverably corrupt evalue" + # keep reference to builtin_mod because the kernel overrides that value # to forward requests to a frontend. -def input(prompt=''): +def input(prompt=""): return builtin_mod.input(prompt) -builtin_mod_name = "builtins" -import builtins as builtin_mod - -MethodType = types.MethodType def execfile(fname, glob, loc=None, compiler=None): loc = loc if (loc is not None) else glob - with open(fname, 'rb') as f: + with open(fname, "rb") as f: compiler = compiler or compile - exec(compiler(f.read(), fname, 'exec'), glob, loc) + exec(compiler(f.read(), fname, "exec"), glob, loc) + PYPY = platform.python_implementation() == "PyPy" @@ -77,6 +61,7 @@ def execfile(fname, glob, loc=None, compiler=None): # See https://github.com/cython/cython/pull/3291 and # https://github.com/ipython/ipython/issues/12068 def no_code(x, encoding=None): - return x -unicode_to_str = cast_bytes_py2 = no_code + return x + +unicode_to_str = cast_bytes_py2 = no_code From b2509e3a3f74995745b607448f86618c02df4274 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 17:04:42 -0800 Subject: [PATCH 1782/3726] Remove deprecated debugger chunks. --- IPython/core/debugger.py | 102 +--------------------------- IPython/core/interactiveshell.py | 6 -- IPython/core/tests/test_debugger.py | 19 ------ IPython/utils/contexts.py | 14 ---- 4 files changed, 3 insertions(+), 138 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index 7163ae130d4..d9095d0c536 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -102,7 +102,6 @@ #***************************************************************************** import bdb -import functools import inspect import linecache import sys @@ -163,94 +162,6 @@ def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): print('Exiting Debugger.') -class Tracer(object): - """ - DEPRECATED - - Class for local debugging, similar to pdb.set_trace. - - Instances of this class, when called, behave like pdb.set_trace, but - providing IPython's enhanced capabilities. - - This is implemented as a class which must be initialized in your own code - and not as a standalone function because we need to detect at runtime - whether IPython is already active or not. That detection is done in the - constructor, ensuring that this code plays nicely with a running IPython, - while functioning acceptably (though with limitations) if outside of it. - """ - - def __init__(self, colors=None): - """ - DEPRECATED - - Create a local debugger instance. - - Parameters - ---------- - colors : str, optional - The name of the color scheme to use, it must be one of IPython's - valid color schemes. If not given, the function will default to - the current IPython scheme when running inside IPython, and to - 'NoColor' otherwise. - - Examples - -------- - :: - - from IPython.core.debugger import Tracer; debug_here = Tracer() - - Later in your code:: - - debug_here() # -> will open up the debugger at that point. - - Once the debugger activates, you can use all of its regular commands to - step through code, set breakpoints, etc. See the pdb documentation - from the Python standard library for usage details. - """ - warnings.warn("`Tracer` is deprecated since version 5.1, directly use " - "`IPython.core.debugger.Pdb.set_trace()`", - DeprecationWarning, stacklevel=2) - - ip = get_ipython() - if ip is None: - # Outside of ipython, we set our own exception hook manually - sys.excepthook = functools.partial(BdbQuit_excepthook, - excepthook=sys.excepthook) - def_colors = 'NoColor' - else: - # In ipython, we use its custom exception handler mechanism - def_colors = ip.colors - ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) - - if colors is None: - colors = def_colors - - # The stdlib debugger internally uses a modified repr from the `repr` - # module, that limits the length of printed strings to a hardcoded - # limit of 30 characters. That much trimming is too aggressive, let's - # at least raise that limit to 80 chars, which should be enough for - # most interactive uses. - try: - from reprlib import aRepr - aRepr.maxstring = 80 - except: - # This is only a user-facing convenience, so any error we encounter - # here can be warned about but can be otherwise ignored. These - # printouts will tell us about problems if this API changes - import traceback - traceback.print_exc() - - self.debugger = Pdb(colors) - - def __call__(self): - """Starts an interactive debugger at the point where called. - - This is similar to the pdb.set_trace() function from the std lib, but - using IPython's enhanced debugger.""" - - self.debugger.set_trace(sys._getframe().f_back) - - RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') @@ -290,14 +201,11 @@ class Pdb(OldPdb): "debuggerskip": True, } - def __init__(self, color_scheme=None, completekey=None, - stdin=None, stdout=None, context=5, **kwargs): + def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs): """Create a new IPython debugger. Parameters ---------- - color_scheme : default None - Deprecated, do not use. completekey : default None Passed to pdb.Pdb. stdin : default None @@ -340,12 +248,8 @@ def __init__(self, color_scheme=None, completekey=None, # the debugger was entered. See also #9941. sys.modules["__main__"] = save_main - if color_scheme is not None: - warnings.warn( - "The `color_scheme` argument is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - else: - color_scheme = self.shell.colors + + color_scheme = self.shell.colors self.aliases = {} diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 519494118ba..39e84ddbd7c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -85,12 +85,6 @@ from typing import List as ListType, Tuple, Optional, Callable from ast import stmt - -# NoOpContext is deprecated, but ipykernel imports it from here. -# See https://github.com/ipython/ipykernel/issues/157 -# (2016, let's try to remove than in IPython 8.0) -from IPython.utils.contexts import NoOpContext - sphinxify: Optional[Callable] try: diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 4b4af229881..65bcb5e2335 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -7,13 +7,8 @@ import bdb import builtins import os -import signal -import subprocess import sys -import time -import warnings -from subprocess import PIPE, CalledProcessError, check_output from tempfile import NamedTemporaryFile from textwrap import dedent from unittest.mock import patch @@ -58,20 +53,6 @@ def __exit__(self, *exc): # Tests #----------------------------------------------------------------------------- -def test_longer_repr(): - from reprlib import repr as trepr - - a = '1234567890'* 7 - ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'" - a_trunc = "'123456789012...8901234567890'" - assert trepr(a) == a_trunc - # The creation of our tracer modifies the repr module's repr function - # in-place, since that global is used directly by the stdlib's pdb module. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - debugger.Tracer() - assert trepr(a) == ar - def test_ipdb_magics(): '''Test calling some IPython magics from ipdb. diff --git a/IPython/utils/contexts.py b/IPython/utils/contexts.py index 4d379b0eda1..7f95d4419dc 100644 --- a/IPython/utils/contexts.py +++ b/IPython/utils/contexts.py @@ -58,17 +58,3 @@ def __exit__(self, *exc_info): for k in self.to_delete: d.pop(k, None) d.update(self.to_update) - - -class NoOpContext(object): - """ - Deprecated - - Context manager that does nothing.""" - - def __init__(self): - warnings.warn("""NoOpContext is deprecated since IPython 5.0 """, - DeprecationWarning, stacklevel=2) - - def __enter__(self): pass - def __exit__(self, type, value, traceback): pass From 944f7c0e608e79a876dfd5c8c34ce58f02c9f93d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Dec 2021 17:23:05 -0800 Subject: [PATCH 1783/3726] Remove many deprecated things from the debugger. Also remove a number of parameters that were deprecated. --- IPython/terminal/embed.py | 48 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index d2cbe888e2d..476359808c9 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -108,6 +108,14 @@ def exit_raise(self, parameter_s=''): self.shell.ask_exit() +class _Sentinel: + def __init__(self, repr): + assert isinstance(repr, str) + self.repr = repr + + def __repr__(self): + return repr + class InteractiveShellEmbed(TerminalInteractiveShell): @@ -148,9 +156,9 @@ def embedded_active(self, value): self._call_location_id) def __init__(self, **kw): - if kw.get('user_global_ns', None) is not None: - raise DeprecationWarning( - "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.") + assert ( + "user_global_ns" not in kw + ), "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0." clid = kw.pop('_init_location_id', None) if not clid: @@ -176,8 +184,16 @@ def init_magics(self): super(InteractiveShellEmbed, self).init_magics() self.register_magics(EmbeddedMagics) - def __call__(self, header='', local_ns=None, module=None, dummy=None, - stack_depth=1, global_ns=None, compile_flags=None, **kw): + def __call__( + self, + header="", + local_ns=None, + module=None, + dummy=None, + stack_depth=1, + compile_flags=None, + **kw + ): """Activate the interactive interpreter. __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start @@ -227,8 +243,9 @@ def __call__(self, header='', local_ns=None, module=None, dummy=None, # Call the embedding code with a stack depth of 1 so it can skip over # our call and get the original caller's namespaces. - self.mainloop(local_ns, module, stack_depth=stack_depth, - global_ns=global_ns, compile_flags=compile_flags) + self.mainloop( + local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags + ) self.banner2 = self.old_banner2 @@ -238,14 +255,19 @@ def __call__(self, header='', local_ns=None, module=None, dummy=None, if self.should_raise: raise KillEmbedded('Embedded IPython raising error, as user requested.') - - def mainloop(self, local_ns=None, module=None, stack_depth=0, - display_banner=None, global_ns=None, compile_flags=None): + def mainloop( + self, + local_ns=None, + module=None, + stack_depth=0, + compile_flags=None, + ): """Embeds IPython into a running python program. Parameters ---------- + local_ns, module Working local namespace (a dict) and module (a module or similar object). If given as None, they are automatically taken from the scope @@ -266,12 +288,6 @@ def mainloop(self, local_ns=None, module=None, stack_depth=0, """ - if (global_ns is not None) and (module is None): - raise DeprecationWarning("'global_ns' keyword argument is deprecated, and has been removed in IPython 5.0 use `module` keyword argument instead.") - - if (display_banner is not None): - warnings.warn("The display_banner parameter is deprecated since IPython 4.0", DeprecationWarning) - # Get locals and globals from caller if ((local_ns is None or module is None or compile_flags is None) and self.default_user_namespaces): From aa169302f4acbbcae616154bf54c70167dbd1fd5 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 4 Dec 2021 19:32:13 +0300 Subject: [PATCH 1784/3726] Everything is an object and has a `__class__` in Python 3 --- IPython/core/oinspect.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 70a64c3952e..35a1461805b 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -182,11 +182,10 @@ def getsource(obj, oname='') -> Union[str,None]: except TypeError: # The object itself provided no meaningful source, try looking for # its class definition instead. - if hasattr(obj, '__class__'): - try: - src = inspect.getsource(obj.__class__) - except TypeError: - return None + try: + src = inspect.getsource(obj.__class__) + except TypeError: + return None return src @@ -308,15 +307,14 @@ def find_file(obj) -> str: fname = None try: fname = inspect.getabsfile(obj) - except (OSError, TypeError): + except TypeError: # For an instance, the file that matters is where its class was # declared. - if hasattr(obj, '__class__'): - try: - fname = inspect.getabsfile(obj.__class__) - except (OSError, TypeError): - # Can happen for builtins - pass + try: + fname = inspect.getabsfile(obj.__class__) + except (OSError, TypeError): + # Can happen for builtins + pass except: pass return cast_unicode(fname) @@ -345,10 +343,7 @@ def find_source_lines(obj): lineno = inspect.getsourcelines(obj)[1] except TypeError: # For instances, try the class object like getsource() does - if hasattr(obj, '__class__'): - lineno = inspect.getsourcelines(obj.__class__)[1] - else: - lineno = None + lineno = inspect.getsourcelines(obj.__class__)[1] except: return None From d001cd1433512185d45cc32d8e24b3530dbee4d5 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 4 Dec 2021 19:42:55 +0300 Subject: [PATCH 1785/3726] Handle OSError from inspect.getsource/getsourcelines (bpo-44648) https://bugs.python.org/issue44648 (Python 3.10+) --- IPython/core/oinspect.py | 17 +++++++++----- IPython/core/tests/test_oinspect.py | 35 ++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 35a1461805b..9e52f1e4771 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -184,8 +184,10 @@ def getsource(obj, oname='') -> Union[str,None]: # its class definition instead. try: src = inspect.getsource(obj.__class__) - except TypeError: + except (OSError, TypeError): return None + except OSError: + return None return src @@ -315,8 +317,9 @@ def find_file(obj) -> str: except (OSError, TypeError): # Can happen for builtins pass - except: + except OSError: pass + return cast_unicode(fname) @@ -339,12 +342,14 @@ def find_source_lines(obj): obj = _get_wrapped(obj) try: + lineno = inspect.getsourcelines(obj)[1] + except TypeError: + # For instances, try the class object like getsource() does try: - lineno = inspect.getsourcelines(obj)[1] - except TypeError: - # For instances, try the class object like getsource() does lineno = inspect.getsourcelines(obj.__class__)[1] - except: + except (OSError, TypeError): + return None + except OSError: return None return lineno diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py index 58d07db8030..94deb356a88 100644 --- a/IPython/core/tests/test_oinspect.py +++ b/IPython/core/tests/test_oinspect.py @@ -6,8 +6,11 @@ from inspect import signature, Signature, Parameter +import inspect import os +import pytest import re +import sys from .. import oinspect @@ -28,6 +31,10 @@ def setup_module(): inspector = oinspect.Inspector() +class SourceModuleMainTest: + __module__ = "__main__" + + #----------------------------------------------------------------------------- # Local utilities #----------------------------------------------------------------------------- @@ -36,15 +43,28 @@ def setup_module(): # defined, if any code is inserted above, the following line will need to be # updated. Do NOT insert any whitespace between the next line and the function # definition below. -THIS_LINE_NUMBER = 39 # Put here the actual number of this line +THIS_LINE_NUMBER = 46 # Put here the actual number of this line + + +def test_find_source_lines(): + assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3 + assert oinspect.find_source_lines(type) is None + assert oinspect.find_source_lines(SourceModuleMainTest) is None + assert oinspect.find_source_lines(SourceModuleMainTest()) is None + -from unittest import TestCase +def test_getsource(): + assert oinspect.getsource(type) is None + assert oinspect.getsource(SourceModuleMainTest) is None + assert oinspect.getsource(SourceModuleMainTest()) is None -class Test(TestCase): - def test_find_source_lines(self): - self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines), - THIS_LINE_NUMBER+6) +def test_inspect_getfile_raises_exception(): + """Check oinspect.find_file/getsource/find_source_lines expectations""" + with pytest.raises(TypeError): + inspect.getfile(type) + with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError): + inspect.getfile(SourceModuleMainTest) # A couple of utilities to ensure these tests work the same from a source or a @@ -59,6 +79,9 @@ def match_pyfiles(f1, f2): def test_find_file(): match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) + assert oinspect.find_file(type) is None + assert oinspect.find_file(SourceModuleMainTest) is None + assert oinspect.find_file(SourceModuleMainTest()) is None def test_find_file_decorated1(): From 3b816f803e98c3db9993ee7dfbd65cc121d4aae2 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sun, 5 Dec 2021 03:52:27 +0300 Subject: [PATCH 1786/3726] Tweak tests for PyPy and add CI runner * `autoreload` tests are skipped on PyPy because `gc.get_referrers` is unusably slow here * `doctest_refs` does not seem to test anything and was removed * pexpect timeouts were adjusted because subprocess communication is 3-4 times slower on PyPy * few other tweaks --- .github/workflows/test.yml | 10 +++++++++ IPython/core/tests/test_async_helpers.py | 3 ++- IPython/core/tests/test_debugger.py | 8 +++++-- IPython/core/tests/test_magic.py | 2 ++ IPython/core/tests/test_run.py | 25 +++++++++++++++------ IPython/core/tests/test_ultratb.py | 6 +++-- IPython/extensions/tests/test_autoreload.py | 8 +++++++ IPython/terminal/tests/test_debug_magic.py | 2 ++ IPython/terminal/tests/test_embed.py | 3 ++- IPython/testing/plugin/show_refs.py | 19 ---------------- IPython/testing/plugin/test_refs.py | 7 ------ IPython/utils/ipstruct.py | 2 +- 12 files changed, 55 insertions(+), 40 deletions(-) delete mode 100644 IPython/testing/plugin/show_refs.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 898dfe5f8d2..5cb06d92e52 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,16 @@ jobs: - os: ubuntu-latest python-version: "3.11-dev" deps: test + # Installing optional dependencies stuff takes ages on PyPy + - os: ubuntu-latest + python-version: "pypy-3.8" + deps: test + - os: windows-latest + python-version: "pypy-3.8" + deps: test + - os: macos-latest + python-version: "pypy-3.8" + deps: test steps: - uses: actions/checkout@v2 diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 9ca67f47e9e..40a840c5b16 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -3,6 +3,7 @@ Should only trigger on python 3.5+ or will have syntax errors. """ +import platform from itertools import chain, repeat from textwrap import dedent, indent from unittest import TestCase @@ -275,7 +276,7 @@ def test_autoawait(self): """ ) - if sys.version_info < (3,9): + if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy": # new pgen parser in 3.9 does not raise MemoryError on too many nested # parens anymore def test_memory_error(self): diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py index 65bcb5e2335..75ce9fd7dcf 100644 --- a/IPython/core/tests/test_debugger.py +++ b/IPython/core/tests/test_debugger.py @@ -371,11 +371,13 @@ def _decorator_skip_setup(): child = pexpect.spawn( sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env ) - child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE child.expect("IPython") child.expect("\n") + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks] in_prompt_number = 1 for cblock in dedented_blocks: @@ -448,11 +450,13 @@ def test_decorator_skip_with_breakpoint(): child = pexpect.spawn( sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env ) - child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE child.expect("IPython") child.expect("\n") + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + ### we need a filename, so we need to exec the full block with a filename with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf: diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 428d7114a35..aa006f63594 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -2,6 +2,7 @@ """Tests for various magic functions.""" import asyncio +import gc import io import os import re @@ -576,6 +577,7 @@ def test_xdel(self): _ip.magic("xdel a") # Check that a's __del__ method has been called. + gc.collect(0) assert monitor == [1] def doctest_who(): diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index a872f5a6620..0f73a781e3e 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -18,6 +18,7 @@ import functools import os +import platform from os.path import join as pjoin import random import string @@ -158,7 +159,7 @@ def doctest_reset_del(): In [3]: a = A() - In [4]: get_ipython().reset() + In [4]: get_ipython().reset(); import gc; x = gc.collect(0) Hi In [5]: 1+1 @@ -241,6 +242,10 @@ def test_simpledef(self): _ip.run_cell("t = isinstance(f(), foo)") assert _ip.user_ns["t"] is True + @pytest.mark.xfail( + platform.python_implementation() == "PyPy", + reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy", + ) def test_obj_del(self): """Test that object's __del__ methods are called on exit.""" src = ("class A(object):\n" @@ -286,14 +291,20 @@ def test_run_second(self): _ip.magic("run %s" % empty.fname) assert _ip.user_ns["afunc"]() == 1 - @dec.skip_win32 def test_tclass(self): mydir = os.path.dirname(__file__) - tc = os.path.join(mydir, 'tclass') - src = ("%%run '%s' C-first\n" - "%%run '%s' C-second\n" - "%%run '%s' C-third\n") % (tc, tc, tc) - self.mktmp(src, '.ipy') + tc = os.path.join(mydir, "tclass") + src = f"""\ +import gc +%run "{tc}" C-first +gc.collect(0) +%run "{tc}" C-second +gc.collect(0) +%run "{tc}" C-third +gc.collect(0) +%reset -f +""" + self.mktmp(src, ".ipy") out = """\ ARGV 1-: ['C-first'] ARGV 1-: ['C-second'] diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index d0f13dff066..66a7a95c122 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -3,6 +3,7 @@ """ import io import logging +import platform import re import sys import os.path @@ -248,7 +249,8 @@ def test_non_syntaxerror(self): ip.showsyntaxerror() import sys -if sys.version_info < (3,9): + +if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy": """ New 3.9 Pgen Parser does not raise Memory error, except on failed malloc. """ @@ -359,7 +361,7 @@ def test_recursion_one_frame(self): ): ip.run_cell("r1()") - @recursionlimit(200) + @recursionlimit(160) def test_recursion_three_frames(self): with tt.AssertPrints("[... skipping similar frames: "), \ tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index e03a08eec97..8cc55e7d28b 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -13,6 +13,8 @@ # ----------------------------------------------------------------------------- import os +import platform +import pytest import sys import tempfile import textwrap @@ -28,6 +30,12 @@ from IPython.extensions.autoreload import AutoreloadMagics from IPython.core.events import EventManager, pre_run_cell +if platform.python_implementation() == "PyPy": + pytest.skip( + "Current autoreload implementation is extremly slow on PyPy", + allow_module_level=True, + ) + # ----------------------------------------------------------------------------- # Test fixture # ----------------------------------------------------------------------------- diff --git a/IPython/terminal/tests/test_debug_magic.py b/IPython/terminal/tests/test_debug_magic.py index d19f8277c19..faa3b7c4993 100644 --- a/IPython/terminal/tests/test_debug_magic.py +++ b/IPython/terminal/tests/test_debug_magic.py @@ -51,6 +51,8 @@ def test_debug_magic_passes_through_generators(): child.sendline(" pass") child.sendline("") + child.timeout = 10 * IPYTHON_TESTING_TIMEOUT_SCALE + child.expect('Exception:') child.expect(in_prompt) diff --git a/IPython/terminal/tests/test_embed.py b/IPython/terminal/tests/test_embed.py index ac4c9424427..3f0885e73cc 100644 --- a/IPython/terminal/tests/test_embed.py +++ b/IPython/terminal/tests/test_embed.py @@ -74,8 +74,9 @@ def test_nest_embed(): child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor'], env=env) - child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE + child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE child.expect(ipy_prompt) + child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE child.sendline("import IPython") child.expect(ipy_prompt) child.sendline("ip0 = get_ipython()") diff --git a/IPython/testing/plugin/show_refs.py b/IPython/testing/plugin/show_refs.py deleted file mode 100644 index b2c70adfc1e..00000000000 --- a/IPython/testing/plugin/show_refs.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Simple script to show reference holding behavior. - -This is used by a companion test case. -""" - -import gc - -class C(object): - def __del__(self): - pass - #print 'deleting object...' # dbg - -if __name__ == '__main__': - c = C() - - c_refs = gc.get_referrers(c) - ref_ids = list(map(id,c_refs)) - - print('c referrers:',list(map(type,c_refs))) diff --git a/IPython/testing/plugin/test_refs.py b/IPython/testing/plugin/test_refs.py index bd7ad8fb3e3..b92448be074 100644 --- a/IPython/testing/plugin/test_refs.py +++ b/IPython/testing/plugin/test_refs.py @@ -37,10 +37,3 @@ def doctest_ivars(): In [6]: zz Out[6]: 1 """ - -def doctest_refs(): - """DocTest reference holding issues when running scripts. - - In [32]: run show_refs.py - c referrers: [<... 'dict'>] - """ diff --git a/IPython/utils/ipstruct.py b/IPython/utils/ipstruct.py index d71a75e22c4..ed112101a36 100644 --- a/IPython/utils/ipstruct.py +++ b/IPython/utils/ipstruct.py @@ -131,7 +131,7 @@ def __getattr__(self, key): >>> s.a 10 >>> type(s.get) - <... 'builtin_function_or_method'> + <...method'> >>> try: ... s.b ... except AttributeError: From eab708fce88447b819c488c228ad901dd1e6d1eb Mon Sep 17 00:00:00 2001 From: Ben Greiner Date: Sat, 4 Dec 2021 17:23:49 +0100 Subject: [PATCH 1787/3726] use alphachannel for aliased png latex --- IPython/lib/latextools.py | 23 ++++++++++++++++++++--- IPython/utils/timing.py | 1 - 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/IPython/lib/latextools.py b/IPython/lib/latextools.py index 540d3f50475..a7bb08dfff9 100644 --- a/IPython/lib/latextools.py +++ b/IPython/lib/latextools.py @@ -161,9 +161,26 @@ def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): resolution = round(150*scale) subprocess.check_call( - ["dvipng", "-T", "tight", "-D", str(resolution), "-z", "9", - "-bg", "transparent", "-o", outfile, dvifile, "-fg", color], - cwd=workdir, stdout=devnull, stderr=devnull) + [ + "dvipng", + "-T", + "tight", + "-D", + str(resolution), + "-z", + "9", + "-bg", + "Transparent", + "-o", + outfile, + dvifile, + "-fg", + color, + ], + cwd=workdir, + stdout=devnull, + stderr=devnull, + ) with outfile.open("rb") as f: return f.read() diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 32741acdd9c..92f6883c4af 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -62,7 +62,6 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] - else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... From 59c23dd2b1927b468e90325284f52b8d8862b8e5 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Mon, 6 Dec 2021 21:32:40 +0300 Subject: [PATCH 1788/3726] Pytest diagnostics improvement for `IPython.testing.tools` * Make Pytest rewrite assertions inside `IPython.testing.tools` to produce informative error reports. * Skip `IPython.testing.tools` functions frames to make error pointing inside the code of the failing test. * Change `ipexec` assertions back to be on string to receive a text diff. --- IPython/conftest.py | 3 +++ IPython/testing/tools.py | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/IPython/conftest.py b/IPython/conftest.py index 8b2af8c020a..fb73d4b2f33 100644 --- a/IPython/conftest.py +++ b/IPython/conftest.py @@ -6,6 +6,9 @@ import pathlib import shutil +# Must register before it gets imported +pytest.register_assert_rewrite("IPython.testing.tools") + from .testing import tools diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 31bfd426863..0d52d88d2bf 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -181,7 +181,10 @@ def ipexec(fname, options=None, commands=()): ------- ``(stdout, stderr)`` of ipython subprocess. """ - if options is None: options = [] + __tracebackhide__ = True + + if options is None: + options = [] cmdargs = default_argv() + options @@ -239,6 +242,7 @@ def ipexec_validate(fname, expected_out, expected_err='', ------- None """ + __tracebackhide__ = True out, err = ipexec(fname, options, commands) #print 'OUT', out # dbg @@ -247,12 +251,16 @@ def ipexec_validate(fname, expected_out, expected_err='', # more informative than simply having an empty stdout. if err: if expected_err: - assert err.strip().splitlines() == expected_err.strip().splitlines() + assert "\n".join(err.strip().splitlines()) == "\n".join( + expected_err.strip().splitlines() + ) else: raise ValueError('Running file %r produced error: %r' % (fname, err)) # If no errors or output on stderr was expected, match stdout - assert out.strip().splitlines() == expected_out.strip().splitlines() + assert "\n".join(out.strip().splitlines()) == "\n".join( + expected_out.strip().splitlines() + ) class TempFileMixin(unittest.TestCase): @@ -312,6 +320,8 @@ def check_pairs(func, pairs): None. Raises an AssertionError if any output does not match the expected value. """ + __tracebackhide__ = True + name = getattr(func, "func_name", getattr(func, "__name__", "")) for inp, expected in pairs: out = func(inp) @@ -354,6 +364,8 @@ def __enter__(self): setattr(sys, self.channel, self.buffer if self.suppress else self.tee) def __exit__(self, etype, value, traceback): + __tracebackhide__ = True + try: if value is not None: # If an error was raised, don't check anything else @@ -381,6 +393,8 @@ class AssertNotPrints(AssertPrints): Counterpart of AssertPrints""" def __exit__(self, etype, value, traceback): + __tracebackhide__ = True + try: if value is not None: # If an error was raised, don't check anything else From c22ed50325ad51c2c5ee080702d7fca96bc8b780 Mon Sep 17 00:00:00 2001 From: nicolaslazo <45973144+nicolaslazo@users.noreply.github.com> Date: Wed, 8 Dec 2021 02:36:48 +0100 Subject: [PATCH 1789/3726] Fix uncaught ValueError in rerun magic int conversion --- IPython/core/magics/history.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/history.py b/IPython/core/magics/history.py index 28f91fa5a82..ad58b33b0b3 100644 --- a/IPython/core/magics/history.py +++ b/IPython/core/magics/history.py @@ -299,7 +299,11 @@ def rerun(self, parameter_s=''): """ opts, args = self.parse_options(parameter_s, 'l:g:', mode='string') if "l" in opts: # Last n lines - n = int(opts['l']) + try: + n = int(opts["l"]) + except ValueError: + print("Number of lines must be an integer") + return if n == 0: print("Requested 0 last lines - nothing to run") From e0b9394e144221da58df39b6fb26947bad5b1d72 Mon Sep 17 00:00:00 2001 From: Andrew Port Date: Tue, 7 Dec 2021 18:39:49 -0800 Subject: [PATCH 1790/3726] Add comma groupings to timeit magic loop count --- IPython/core/magics/execution.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index d74f4c2de2f..1b1c7b77940 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -92,17 +92,15 @@ def __str__(self): pm = u'\xb1' except: pass - return ( - u"{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops} loop{loop_plural} each)" - .format( - pm = pm, - runs = self.repeat, - loops = self.loops, - loop_plural = "" if self.loops == 1 else "s", - run_plural = "" if self.repeat == 1 else "s", - mean = _format_time(self.average, self._precision), - std = _format_time(self.stdev, self._precision)) - ) + return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format( + pm=pm, + runs=self.repeat, + loops=self.loops, + loop_plural="" if self.loops == 1 else "s", + run_plural="" if self.repeat == 1 else "s", + mean=_format_time(self.average, self._precision), + std=_format_time(self.stdev, self._precision), + ) def _repr_pretty_(self, p , cycle): unic = self.__str__() From 96f8b20fb716570b869d05740e249b4fabef8e00 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 09:19:57 -0800 Subject: [PATCH 1791/3726] Start deprecating %%javascript/%%js See #13376 --- IPython/core/magics/display.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/display.py b/IPython/core/magics/display.py index c4350e404d2..6c0eff6884f 100644 --- a/IPython/core/magics/display.py +++ b/IPython/core/magics/display.py @@ -36,12 +36,23 @@ def js(self, line, cell): """Run the cell block of Javascript code Alias of `%%javascript` + + Starting with IPython 8.0 %%javascript is pending deprecation to be replaced + by a more flexible system + + Please See https://github.com/ipython/ipython/issues/13376 """ self.javascript(line, cell) @cell_magic def javascript(self, line, cell): - """Run the cell block of Javascript code""" + """Run the cell block of Javascript code + + Starting with IPython 8.0 %%javascript is pending deprecation to be replaced + by a more flexible system + + Please See https://github.com/ipython/ipython/issues/13376 + """ display(Javascript(cell)) From d4a3285599a32e425eb471dfcc1d3b72ceebc428 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 19:15:14 -0400 Subject: [PATCH 1792/3726] Remove building scripts manually, console_scripts is preferred --- setup.py | 1 - setupbase.py | 46 ---------------------------------------------- 2 files changed, 47 deletions(-) diff --git a/setup.py b/setup.py index 69279cdf438..d8d416fad3e 100755 --- a/setup.py +++ b/setup.py @@ -77,7 +77,6 @@ find_package_data, check_package_data_first, find_entry_points, - build_scripts_entrypt, find_data_files, git_prebuild, install_symlinked, diff --git a/setupbase.py b/setupbase.py index 97cebe807e1..646388f6bb8 100644 --- a/setupbase.py +++ b/setupbase.py @@ -21,8 +21,6 @@ from setuptools import Command from setuptools.command.build_py import build_py -# TODO: Replacement for this? -from distutils.command.build_scripts import build_scripts from setuptools.command.install import install from setuptools.command.install_scripts import install_scripts @@ -238,50 +236,6 @@ def find_entry_points(): suffix = str(sys.version_info[0]) return [e % '' for e in ep] + [e % suffix for e in ep] -script_src = """#!{executable} -# This script was automatically generated by setup.py -if __name__ == '__main__': - from {mod} import {func} - {func}() -""" - -class build_scripts_entrypt(build_scripts): - """Build the command line scripts - - Parse setuptools style entry points and write simple scripts to run the - target functions. - - On Windows, this also creates .cmd wrappers for the scripts so that you can - easily launch them from a command line. - """ - def run(self): - self.mkpath(self.build_dir) - outfiles = [] - for script in find_entry_points(): - name, entrypt = script.split('=') - name = name.strip() - entrypt = entrypt.strip() - outfile = os.path.join(self.build_dir, name) - outfiles.append(outfile) - print('Writing script to', outfile) - - mod, func = entrypt.split(':') - with open(outfile, 'w') as f: - f.write(script_src.format(executable=sys.executable, - mod=mod, func=func)) - - if sys.platform == 'win32': - # Write .cmd wrappers for Windows so 'ipython' etc. work at the - # command line - cmd_file = os.path.join(self.build_dir, name + '.cmd') - cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format( - python=sys.executable, script=name) - log.info("Writing %s wrapper script" % cmd_file) - with open(cmd_file, 'w') as f: - f.write(cmd) - - return outfiles, outfiles - class install_lib_symlink(Command): user_options = [ ('install-dir=', 'd', "directory to install to"), From 7819f4889810d3a13fde159de6f92497ae5348b6 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 19:28:03 -0400 Subject: [PATCH 1793/3726] Only use setuptools, avoid distutils as much as possible --- setup.py | 51 ++++++++++++++------------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/setup.py b/setup.py index d8d416fad3e..5ff71640126 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,6 @@ import os import sys -from pathlib import Path # **Python version check** # @@ -61,12 +60,7 @@ # At least we're on the python version we need, move on. -# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly -# update it when the contents of directories change. -if Path("MANIFEST").exists(): - Path("MANIFEST").unlink() - -from distutils.core import setup +from setuptools import setup # Our own imports from setupbase import target_update @@ -137,7 +131,7 @@ # custom distutils commands #--------------------------------------------------------------------------- # imports here, so they are after setuptools import if there was one -from distutils.command.sdist import sdist +from setuptools.command.sdist import sdist setup_args['cmdclass'] = { 'build_py': \ @@ -154,16 +148,6 @@ # Handle scripts, dependencies, and setuptools specific things #--------------------------------------------------------------------------- -# For some commands, use setuptools. Note that we do NOT list install here! -# If you want a setuptools-enhanced install, just run 'setupegg.py install' -needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm', - 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel', - 'egg_info', 'easy_install', 'upload', 'install_egg_info', - } - -if len(needs_setuptools.intersection(sys.argv)) > 0: - import setuptools - # This dict is used for passing extra arguments that are setuptools # specific to setup setuptools_extra_args = {} @@ -244,25 +228,18 @@ everything.update(deps) extras_require['all'] = list(sorted(everything)) -if "setuptools" in sys.modules: - setuptools_extra_args["python_requires"] = ">=3.8" - setuptools_extra_args["zip_safe"] = False - setuptools_extra_args["entry_points"] = { - "console_scripts": find_entry_points(), - "pygments.lexers": [ - "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer", - "ipython = IPython.lib.lexers:IPythonLexer", - "ipython3 = IPython.lib.lexers:IPython3Lexer", - ], - } - setup_args['extras_require'] = extras_require - setup_args['install_requires'] = install_requires - -else: - # scripts has to be a non-empty list, or install_scripts isn't called - setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()] - - setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt +setuptools_extra_args["python_requires"] = ">=3.8" +setuptools_extra_args["zip_safe"] = False +setuptools_extra_args["entry_points"] = { + "console_scripts": find_entry_points(), + "pygments.lexers": [ + "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer", + "ipython = IPython.lib.lexers:IPythonLexer", + "ipython3 = IPython.lib.lexers:IPython3Lexer", + ], +} +setup_args["extras_require"] = extras_require +setup_args["install_requires"] = install_requires #--------------------------------------------------------------------------- # Do the actual setup now From 615a72675830882cd616c9229123a573fcedca00 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 19:32:49 -0400 Subject: [PATCH 1794/3726] Move project_urls to setup.cfg --- setup.cfg | 5 +++++ setupbase.py | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/setup.cfg b/setup.cfg index 31a467ccbf3..ae8e92154ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,10 @@ [metadata] license_file = LICENSE +project_urls = + Documentation = https://ipython.readthedocs.io/ + Funding = https://numfocus.org/ + Source = https://github.com/ipython/ipython + Tracker = https://github.com/ipython/ipython/issues [velin] ignore_patterns = diff --git a/setupbase.py b/setupbase.py index 646388f6bb8..135fac1b65d 100644 --- a/setupbase.py +++ b/setupbase.py @@ -74,12 +74,6 @@ def file_doesnt_endwith(test,endings): keywords = keywords, classifiers = classifiers, cmdclass = {'install_data': install_data_ext}, - project_urls={ - 'Documentation': 'https://ipython.readthedocs.io/', - 'Funding' : 'https://numfocus.org/', - 'Source' : 'https://github.com/ipython/ipython', - 'Tracker' : 'https://github.com/ipython/ipython/issues', - } ) From dedf015a971e4a9aa11865f612d460414d9286d8 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 20:12:16 -0400 Subject: [PATCH 1795/3726] Set version in setup.cfg --- setup.cfg | 1 + setupbase.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ae8e92154ae..f1e1cf836bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,5 @@ [metadata] +version = attr: IPython.core.release.__version__ license_file = LICENSE project_urls = Documentation = https://ipython.readthedocs.io/ diff --git a/setupbase.py b/setupbase.py index 135fac1b65d..5a92b0db211 100644 --- a/setupbase.py +++ b/setupbase.py @@ -63,7 +63,6 @@ def file_doesnt_endwith(test,endings): # This dict is eventually passed to setup after additional keys are added. setup_args = dict( name = name, - version = version, description = description, long_description = long_description, author = author, From 77651b7a7331b1b030f1c2c03310bf7ebb242488 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 20:34:51 -0400 Subject: [PATCH 1796/3726] Specify install_requires in setup.cfg --- setup.cfg | 16 ++++++++++++++++ setup.py | 42 +----------------------------------------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/setup.cfg b/setup.cfg index f1e1cf836bd..5dd19c5c2d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,22 @@ project_urls = Source = https://github.com/ipython/ipython Tracker = https://github.com/ipython/ipython/issues +[options] +install_requires = + setuptools>=18.5 + jedi>=0.16 + decorator + pickleshare + traitlets>=4.2 + prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1 + pygments + backcall + stack_data + matplotlib-inline + pexpect>4.3; sys_platform != "win32" + appnope; sys_platform == "darwin" + colorama; sys_platform == "win32" + [velin] ignore_patterns = IPython/core/tests, diff --git a/setup.py b/setup.py index 5ff71640126..04314424e59 100755 --- a/setup.py +++ b/setup.py @@ -181,46 +181,6 @@ nbconvert=["nbconvert"], ) -install_requires = [ - "setuptools>=18.5", - "jedi>=0.16", - "decorator", - "pickleshare", - "traitlets>=4.2", - "prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1", - "pygments", - "backcall", - "stack_data", - "matplotlib-inline", -] - -# Platform-specific dependencies: -# This is the correct way to specify these, -# but requires pip >= 6. pip < 6 ignores these. - -extras_require.update( - { - ':sys_platform != "win32"': ["pexpect>4.3"], - ':sys_platform == "darwin"': ["appnope"], - ':sys_platform == "win32"': ["colorama"], - } -) -# FIXME: re-specify above platform dependencies for pip < 6 -# These would result in non-portable bdists. -if not any(arg.startswith('bdist') for arg in sys.argv): - if sys.platform == 'darwin': - install_requires.extend(['appnope']) - - if not sys.platform.startswith("win"): - install_requires.append("pexpect>4.3") - - # workaround pypa/setuptools#147, where setuptools misspells - # platform_python_implementation as python_implementation - if 'setuptools' in sys.modules: - for key in list(extras_require): - if 'platform_python_implementation' in key: - new_key = key.replace('platform_python_implementation', 'python_implementation') - extras_require[new_key] = extras_require.pop(key) everything = set() for key, deps in extras_require.items(): @@ -238,8 +198,8 @@ "ipython3 = IPython.lib.lexers:IPython3Lexer", ], } + setup_args["extras_require"] = extras_require -setup_args["install_requires"] = install_requires #--------------------------------------------------------------------------- # Do the actual setup now From c512199dd19f3413c7f5951cec43b1f9dd69e40f Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 20:36:02 -0400 Subject: [PATCH 1797/3726] Put pygments.lexers entry point in setup.cfg --- setup.cfg | 6 ++++++ setup.py | 9 +-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index 5dd19c5c2d3..5223f826879 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,12 @@ install_requires = appnope; sys_platform == "darwin" colorama; sys_platform == "win32" +[options.entry_points] +pygments.lexers = + ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer + ipython = IPython.lib.lexers:IPythonLexer + ipython3 = IPython.lib.lexers:IPython3Lexer + [velin] ignore_patterns = IPython/core/tests, diff --git a/setup.py b/setup.py index 04314424e59..8d119abee77 100755 --- a/setup.py +++ b/setup.py @@ -190,14 +190,7 @@ setuptools_extra_args["python_requires"] = ">=3.8" setuptools_extra_args["zip_safe"] = False -setuptools_extra_args["entry_points"] = { - "console_scripts": find_entry_points(), - "pygments.lexers": [ - "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer", - "ipython = IPython.lib.lexers:IPythonLexer", - "ipython3 = IPython.lib.lexers:IPython3Lexer", - ], -} +setuptools_extra_args["entry_points"] = {"console_scripts": find_entry_points()} setup_args["extras_require"] = extras_require From b829a038a9459f01d65a667cbc64e910310e19db Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 20:38:42 -0400 Subject: [PATCH 1798/3726] Put zip_safe and python_requires in setup.cfg --- setup.cfg | 2 ++ setup.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 5223f826879..a13d299fd21 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,8 @@ project_urls = Tracker = https://github.com/ipython/ipython/issues [options] +python_requires = >=3.8 +zip_safe = False install_requires = setuptools>=18.5 jedi>=0.16 diff --git a/setup.py b/setup.py index 8d119abee77..9a2925d766b 100755 --- a/setup.py +++ b/setup.py @@ -188,10 +188,7 @@ everything.update(deps) extras_require['all'] = list(sorted(everything)) -setuptools_extra_args["python_requires"] = ">=3.8" -setuptools_extra_args["zip_safe"] = False setuptools_extra_args["entry_points"] = {"console_scripts": find_entry_points()} - setup_args["extras_require"] = extras_require #--------------------------------------------------------------------------- From ac21a9b6d68c0e6f42527282aefb49f06aacc781 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 20:44:55 -0400 Subject: [PATCH 1799/3726] Specify console_scripts in setup.cfg --- setup.cfg | 5 +++++ setup.py | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index a13d299fd21..2209f4274b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,11 @@ install_requires = colorama; sys_platform == "win32" [options.entry_points] +console_scripts = + ipython = IPython:start_ipython + iptest = IPython.testing.iptestcontroller:main + ipython3 = IPython:start_ipython + iptest3 = IPython.testing.iptestcontroller:main pygments.lexers = ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer ipython = IPython.lib.lexers:IPythonLexer diff --git a/setup.py b/setup.py index 9a2925d766b..863f1fdf409 100755 --- a/setup.py +++ b/setup.py @@ -70,7 +70,6 @@ find_packages, find_package_data, check_package_data_first, - find_entry_points, find_data_files, git_prebuild, install_symlinked, @@ -188,7 +187,6 @@ everything.update(deps) extras_require['all'] = list(sorted(everything)) -setuptools_extra_args["entry_points"] = {"console_scripts": find_entry_points()} setup_args["extras_require"] = extras_require #--------------------------------------------------------------------------- From ca92ec6af89bdb43b8b72da0a91b46bb5ebd2649 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 21:07:00 -0400 Subject: [PATCH 1800/3726] Use setuptools' find_packages() --- setup.cfg | 9 +++++++-- setup.py | 3 --- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2209f4274b7..fb632bb1109 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,7 @@ project_urls = Tracker = https://github.com/ipython/ipython/issues [options] +packages = find: python_requires = >=3.8 zip_safe = False install_requires = @@ -25,12 +26,16 @@ install_requires = appnope; sys_platform == "darwin" colorama; sys_platform == "win32" +[options.packages.find] +exclude = + deathrow + quarantine + setupext + [options.entry_points] console_scripts = ipython = IPython:start_ipython - iptest = IPython.testing.iptestcontroller:main ipython3 = IPython:start_ipython - iptest3 = IPython.testing.iptestcontroller:main pygments.lexers = ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer ipython = IPython.lib.lexers:IPythonLexer diff --git a/setup.py b/setup.py index 863f1fdf409..eb2a11fd31d 100755 --- a/setup.py +++ b/setup.py @@ -67,7 +67,6 @@ from setupbase import ( setup_args, - find_packages, find_package_data, check_package_data_first, find_data_files, @@ -117,12 +116,10 @@ # Find all the packages, package data, and data_files #--------------------------------------------------------------------------- -packages = find_packages() package_data = find_package_data() data_files = find_data_files() -setup_args['packages'] = packages setup_args['package_data'] = package_data setup_args['data_files'] = data_files From 8eedf6a2134f3a9b627997fe12d281722974b9ef Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 21:13:02 -0400 Subject: [PATCH 1801/3726] Specify package data in setup.cfg --- setup.cfg | 6 ++++++ setup.py | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index fb632bb1109..f94d002ebc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,12 @@ exclude = quarantine setupext +[options.package_data] +IPython.core = profile/README* +IPython.core.tests = *.png, *.jpg, daft_extension/*.py +IPython.lib.tests = *.wav +IPython.testing.plugin = *.txt + [options.entry_points] console_scripts = ipython = IPython:start_ipython diff --git a/setup.py b/setup.py index eb2a11fd31d..ebff3fdd49a 100755 --- a/setup.py +++ b/setup.py @@ -67,7 +67,6 @@ from setupbase import ( setup_args, - find_package_data, check_package_data_first, find_data_files, git_prebuild, @@ -116,11 +115,8 @@ # Find all the packages, package data, and data_files #--------------------------------------------------------------------------- -package_data = find_package_data() - data_files = find_data_files() -setup_args['package_data'] = package_data setup_args['data_files'] = data_files #--------------------------------------------------------------------------- From a64d0eeda59856d6c631139a701507b9bdc8d103 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 21:14:58 -0400 Subject: [PATCH 1802/3726] Remove obsolete setuptools_extra_args --- setup.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/setup.py b/setup.py index ebff3fdd49a..9004ff51685 100755 --- a/setup.py +++ b/setup.py @@ -140,10 +140,6 @@ # Handle scripts, dependencies, and setuptools specific things #--------------------------------------------------------------------------- -# This dict is used for passing extra arguments that are setuptools -# specific to setup -setuptools_extra_args = {} - # setuptools requirements extras_require = dict( @@ -186,10 +182,6 @@ # Do the actual setup now #--------------------------------------------------------------------------- -setup_args.update(setuptools_extra_args) - - - def main(): setup(**setup_args) From f30b345ac0b90d9f197103ca88dedbd885b3da95 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 25 Oct 2021 21:32:08 -0400 Subject: [PATCH 1803/3726] Move more metadata from IPython.core.release.py to setup.cfg --- IPython/core/release.py | 65 ----------------------------------------- setup.cfg | 54 ++++++++++++++++++++++++++++++++++ setupbase.py | 7 ----- 3 files changed, 54 insertions(+), 72 deletions(-) diff --git a/IPython/core/release.py b/IPython/core/release.py index b1c61d9bf48..402c2ed100b 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -12,10 +12,6 @@ # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- -# Name of the package for release purposes. This is the name which labels -# the tarballs and RPMs made by distutils, so it's best to lowercase it. -name = 'ipython' - # IPython version information. An empty _version_extra corresponds to a full # release. 'dev' as a _version_extra string means this is a development # version @@ -40,49 +36,6 @@ kernel_protocol_version_info = (5, 0) kernel_protocol_version = "%i.%i" % kernel_protocol_version_info -description = "IPython: Productive Interactive Computing" - -long_description = \ -""" -IPython provides a rich toolkit to help you make the most out of using Python -interactively. Its main components are: - -* A powerful interactive Python shell -* A `Jupyter `_ kernel to work with Python code in Jupyter - notebooks and other interactive frontends. - -The enhanced interactive Python shells have the following main features: - -* Comprehensive object introspection. - -* Input history, persistent across sessions. - -* Caching of output results during a session with automatically generated - references. - -* Extensible tab completion, with support by default for completion of python - variables and keywords, filenames and function keywords. - -* Extensible system of 'magic' commands for controlling the environment and - performing many tasks related either to IPython or the operating system. - -* A rich configuration system with easy switching between different setups - (simpler than changing $PYTHONSTARTUP environment variables every time). - -* Session logging and reloading. - -* Extensible syntax processing for special purpose situations. - -* Access to the system shell with user-extensible alias system. - -* Easily embeddable in other Python programs and GUIs. - -* Integrated access to the pdb debugger and the Python profiler. - -The latest development version is always available from IPython's `GitHub -site `_. -""" - license = 'BSD' authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), @@ -99,21 +52,3 @@ author = 'The IPython Development Team' author_email = 'ipython-dev@python.org' - -url = 'https://ipython.org' - - -platforms = ['Linux','Mac OSX','Windows'] - -keywords = ['Interactive','Interpreter','Shell', 'Embedding'] - -classifiers = [ - 'Framework :: IPython', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: System :: Shells' - ] diff --git a/setup.cfg b/setup.cfg index f94d002ebc5..c1de3021fe4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,65 @@ [metadata] +name = ipython version = attr: IPython.core.release.__version__ +url = https://ipython.org +description = IPython: Productive Interactive Computing +long_description_content_type = text/x-rst +long_description = IPython provides a rich toolkit to help you make the most out of using Python + interactively. Its main components are: + + * A powerful interactive Python shell + * A `Jupyter `_ kernel to work with Python code in Jupyter + notebooks and other interactive frontends. + + The enhanced interactive Python shells have the following main features: + + * Comprehensive object introspection. + + * Input history, persistent across sessions. + + * Caching of output results during a session with automatically generated + references. + + * Extensible tab completion, with support by default for completion of python + variables and keywords, filenames and function keywords. + + * Extensible system of 'magic' commands for controlling the environment and + performing many tasks related either to IPython or the operating system. + + * A rich configuration system with easy switching between different setups + (simpler than changing $PYTHONSTARTUP environment variables every time). + + * Session logging and reloading. + + * Extensible syntax processing for special purpose situations. + + * Access to the system shell with user-extensible alias system. + + * Easily embeddable in other Python programs and GUIs. + + * Integrated access to the pdb debugger and the Python profiler. + + The latest development version is always available from IPython's `GitHub + site `_. + license_file = LICENSE project_urls = Documentation = https://ipython.readthedocs.io/ Funding = https://numfocus.org/ Source = https://github.com/ipython/ipython Tracker = https://github.com/ipython/ipython/issues +keywords = Interactive, Interpreter, Shell, Embedding +platforms = Linux, Mac OSX, Windows +classifiers = + Framework :: IPython + Intended Audience :: Developers + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Topic :: System :: Shell + [options] packages = find: diff --git a/setupbase.py b/setupbase.py index 5a92b0db211..110098595d0 100644 --- a/setupbase.py +++ b/setupbase.py @@ -62,16 +62,9 @@ def file_doesnt_endwith(test,endings): # Create a dict with the basic information # This dict is eventually passed to setup after additional keys are added. setup_args = dict( - name = name, - description = description, - long_description = long_description, author = author, author_email = author_email, - url = url, license = license, - platforms = platforms, - keywords = keywords, - classifiers = classifiers, cmdclass = {'install_data': install_data_ext}, ) From 2aec64a92a49b380244d86ed2a4499d0553b5f3b Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 26 Oct 2021 21:20:00 -0400 Subject: [PATCH 1804/3726] Use python's gzip module instead of the gzip command for compatibility --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9004ff51685..d836e2c9c88 100755 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ to_update = [ ('docs/man/ipython.1.gz', ['docs/man/ipython.1'], - 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'), + 'cd docs/man && python -m gzip --best ipython.1'), ] From df2197c590b6636cb827ad6fef1e5a1ef087efeb Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 26 Oct 2021 21:36:23 -0400 Subject: [PATCH 1805/3726] Simplify constructing 'all' extra --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d836e2c9c88..8df82e5e1c9 100755 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ import os import sys +from itertools import chain # **Python version check** # @@ -170,10 +171,7 @@ ) -everything = set() -for key, deps in extras_require.items(): - if ':' not in key: - everything.update(deps) +everything = set(chain.from_iterable(extras_require.values())) extras_require['all'] = list(sorted(everything)) setup_args["extras_require"] = extras_require From 84fa21b3775d343588d08c82d4214b6ea9873935 Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 26 Oct 2021 22:37:01 -0400 Subject: [PATCH 1806/3726] PEP 517 support --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..c68b8c200f3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools >= 51.0.0", "wheel"] +build-backend = "setuptools.build_meta:__legacy__" From 6d7c4f84226566ab68e2827ff575ef521e67e782 Mon Sep 17 00:00:00 2001 From: James Morris Date: Sat, 30 Oct 2021 01:32:30 -0400 Subject: [PATCH 1807/3726] Make setup.py not directly runnable --- setup.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) mode change 100755 => 100644 setup.py diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 8df82e5e1c9..0ed75219327 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Setup script for IPython. @@ -180,8 +179,4 @@ # Do the actual setup now #--------------------------------------------------------------------------- -def main(): - setup(**setup_args) - -if __name__ == '__main__': - main() +setup(**setup_args) From 36d0888bba9f8f01ad243788ca11507a837cf8ad Mon Sep 17 00:00:00 2001 From: James Morris Date: Sat, 30 Oct 2021 01:32:49 -0400 Subject: [PATCH 1808/3726] Remove obsolete setupegg.py --- setupegg.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100755 setupegg.py diff --git a/setupegg.py b/setupegg.py deleted file mode 100755 index 7b4b5687e4b..00000000000 --- a/setupegg.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -"""Wrapper to run setup.py using setuptools.""" - -# Import setuptools and call the actual setup -import setuptools -with open('setup.py', 'rb') as f: - exec(compile(f.read(), 'setup.py', 'exec')) From 2cd2d4ce41846c4e0ab0b81d0aa6463ad0ed6568 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 15:35:17 -0800 Subject: [PATCH 1809/3726] updates --- setup.cfg | 4 +--- setup.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index c1de3021fe4..f2778621a6b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,7 +70,7 @@ install_requires = jedi>=0.16 decorator pickleshare - traitlets>=4.2 + traitlets>=5 prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1 pygments backcall @@ -82,8 +82,6 @@ install_requires = [options.packages.find] exclude = - deathrow - quarantine setupext [options.package_data] diff --git a/setup.py b/setup.py index 0ed75219327..141b3d3585a 100644 --- a/setup.py +++ b/setup.py @@ -179,4 +179,5 @@ # Do the actual setup now #--------------------------------------------------------------------------- -setup(**setup_args) +if __name__ == "__main__": + setup(**setup_args) From f5a9499dfdf73da6b1c8eb3431a82d0290ad2a74 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 15:48:43 -0800 Subject: [PATCH 1810/3726] reformat --- IPython/utils/timing.py | 1 + setup.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 92f6883c4af..32741acdd9c 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -62,6 +62,7 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] + else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... diff --git a/setup.py b/setup.py index 141b3d3585a..4c52f47478e 100644 --- a/setup.py +++ b/setup.py @@ -103,10 +103,12 @@ # List of things to be updated. Each entry is a triplet of args for # target_update() to_update = [ - ('docs/man/ipython.1.gz', - ['docs/man/ipython.1'], - 'cd docs/man && python -m gzip --best ipython.1'), - ] + ( + "docs/man/ipython.1.gz", + ["docs/man/ipython.1"], + "cd docs/man && python -m gzip --best ipython.1", + ), + ] [ target_update(*t) for t in to_update ] From 32153e635e4d9e17ff3726e92e0d3f120391148e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:02:08 -0800 Subject: [PATCH 1811/3726] try to build with python -m build --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cb06d92e52..b24c6ff5e93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,9 +55,14 @@ jobs: run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng - name: Install and update Python dependencies run: | - python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip setuptools wheel build python -m pip install --upgrade -e .[${{ matrix.deps }}] python -m pip install --upgrade check-manifest pytest-cov + - name: Try building with Python build + if: runner.os != 'Windows' # setup.py does not support sdist on Windows + run: | + python -m build + shasum -a 256 dist/* - name: Check manifest if: runner.os != 'Windows' # setup.py does not support sdist on Windows run: check-manifest From e42fd674fc2f1e408cb1d40985ce71f2c4335467 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:03:22 -0800 Subject: [PATCH 1812/3726] setup pip cache --- .github/workflows/test.yml | 1 + IPython/utils/timing.py | 1 - setup.cfg | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b24c6ff5e93..e53fb5c9f1e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,6 +50,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + cache: pip - name: Install latex if: runner.os == 'Linux' && matrix.deps == 'test_extra' run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 32741acdd9c..92f6883c4af 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -62,7 +62,6 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] - else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... diff --git a/setup.cfg b/setup.cfg index f2778621a6b..837dca3e39a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,3 +103,6 @@ pygments.lexers = ignore_patterns = IPython/core/tests, IPython/testing + +[tool.black] +exclude = 'timing\.py' From e7fd431d8b1fd4fd032190d1d9bb1d373d97b574 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 15:27:43 -0800 Subject: [PATCH 1813/3726] Remove many deprecation and bump traitlets to 5+ Many old hooks from debugger removed, We now are traitlets 5+ --- IPython/core/application.py | 10 ------ IPython/core/debugger.py | 15 +++----- IPython/core/formatters.py | 12 +++---- IPython/core/interactiveshell.py | 13 ++++--- IPython/core/shellapp.py | 18 ---------- IPython/core/tests/test_formatters.py | 12 ------- IPython/terminal/ipapp.py | 16 --------- IPython/utils/path.py | 50 +-------------------------- IPython/utils/timing.py | 1 + setup.py | 1 - 10 files changed, 20 insertions(+), 128 deletions(-) diff --git a/IPython/core/application.py b/IPython/core/application.py index 85c91274d4e..2b26d4fad82 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -256,16 +256,6 @@ def __init__(self, **kwargs): # Various stages of Application creation #------------------------------------------------------------------------- - deprecated_subcommands = {} - - def initialize_subcommand(self, subc, argv=None): - if subc in self.deprecated_subcommands: - self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed " - "in future versions.".format(sub=subc)) - self.log.warning("You likely want to use `jupyter {sub}` in the " - "future".format(sub=subc)) - return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv) - def init_crash_handler(self): """Create a crash handler, typically setting sys.excepthook to it.""" self.crash_handler = self.crash_handler_class(self) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d9095d0c536..d3e4ec2defe 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -144,22 +144,15 @@ def BdbQuit_excepthook(et, ev, tb, excepthook=None): All other exceptions are processed using the `excepthook` parameter. """ - warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - if et == bdb.BdbQuit: - print('Exiting Debugger.') - elif excepthook is not None: - excepthook(et, ev, tb) - else: - # Backwards compatibility. Raise deprecation warning? - BdbQuit_excepthook.excepthook_ori(et, ev, tb) + raise ValueError( + "`BdbQuit_excepthook` is deprecated since version 5.1", + ) def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): - warnings.warn( + raise ValueError( "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", DeprecationWarning, stacklevel=2) - print('Exiting Debugger.') RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 07efecab40a..b2430f0ed60 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -834,13 +834,11 @@ def _check_return(self, r, obj): if isinstance(r, tuple): # unpack data, metadata tuple for type checking on first element r, md = r - - # handle deprecated JSON-as-string form from IPython < 3 - if isinstance(r, str): - warnings.warn("JSON expects JSONable list/dict containers, not JSON strings", - FormatterWarning) - r = json.loads(r) - + + assert not isinstance( + r, str + ), "JSON-as-string has been deprecated since IPython < 3" + if md is not None: # put the tuple back together r = (r, md) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 39e84ddbd7c..020e892f543 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -906,7 +906,11 @@ def set_hook(self,name,hook, priority=50, str_key=None, re_key=None, if _warn_deprecated and (name in IPython.core.hooks.deprecated): alternative = IPython.core.hooks.deprecated[name] - warn("Hook {} is deprecated. Use {} instead.".format(name, alternative), stacklevel=2) + raise ValueError( + "Hook {} has been deprecated since IPython 5.0. Use {} instead.".format( + name, alternative + ) + ) if not dp: dp = IPython.core.hooks.CommandChainDispatcher() @@ -933,9 +937,10 @@ def register_post_execute(self, func): Register a function for calling after code execution. """ - warn("ip.register_post_execute is deprecated, use " - "ip.events.register('post_run_cell', func) instead.", stacklevel=2) - self.events.register('post_run_cell', func) + raise ValueError( + "ip.register_post_execute is deprecated since IPython 1.0, use " + "ip.events.register('post_run_cell', func) instead." + ) def _clear_warning_registry(self): # clear the warning registry, so that different code blocks with diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index c442658ae70..f737bcb56b7 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -98,11 +98,6 @@ ) shell_aliases['cache-size'] = 'InteractiveShell.cache_size' -if traitlets.version_info < (5, 0): - # traitlets 4 doesn't handle lists on CLI - shell_aliases["ext"] = "InteractiveShellApp.extra_extension" - - #----------------------------------------------------------------------------- # Main classes and functions #----------------------------------------------------------------------------- @@ -126,17 +121,6 @@ class InteractiveShellApp(Configurable): help="A list of dotted module names of IPython extensions to load." ).tag(config=True) - extra_extension = Unicode( - "", - help=""" - DEPRECATED. Dotted module name of a single extra IPython extension to load. - - Only one extension can be added this way. - - Only used with traitlets < 5.0, plural extra_extensions list is used in traitlets 5. - """, - ).tag(config=True) - extra_extensions = List( DottedObjectName(), help=""" @@ -293,8 +277,6 @@ def init_extensions(self): extensions = ( self.default_extensions + self.extensions + self.extra_extensions ) - if self.extra_extension: - extensions.append(self.extra_extension) for ext in extensions: try: self.log.info("Loading IPython extension: %s" % ext) diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 6ea40eb22b6..26d35837ac8 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -430,18 +430,6 @@ def _ipython_display_(self): f.ipython_display_formatter.enabled = save_enabled -def test_json_as_string_deprecated(): - class JSONString(object): - def _repr_json_(self): - return '{}' - - f = JSONFormatter() - with warnings.catch_warnings(record=True) as w: - d = f(JSONString()) - assert d == {} - assert len(w) == 1 - - def test_repr_mime(): class HasReprMime(object): def _repr_mimebundle_(self, include=None, exclude=None): diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py index b95266eaef0..ed39b7dc5d0 100755 --- a/IPython/terminal/ipapp.py +++ b/IPython/terminal/ipapp.py @@ -262,22 +262,6 @@ def _file_to_run_changed(self, change): # internal, not-configurable something_to_run=Bool(False) - def parse_command_line(self, argv=None): - """override to allow old '-pylab' flag with deprecation warning""" - - argv = sys.argv[1:] if argv is None else argv - - if '-pylab' in argv: - # deprecated `-pylab` given, - # warn and transform into current syntax - argv = argv[:] # copy, don't clobber - idx = argv.index('-pylab') - warnings.warn("`-pylab` flag has been deprecated.\n" - " Use `--matplotlib ` and import pylab manually.") - argv[idx] = '--pylab' - - return super(TerminalIPythonApp, self).parse_command_line(argv) - @catch_config_error def initialize(self, argv=None): """Do actions after construct, but before starting the app.""" diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 273c519a004..375ae384141 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -67,20 +67,6 @@ def get_long_path_name(path): return _get_long_path_name(path) -def unquote_filename(name, win32=(sys.platform=='win32')): - """ On Windows, remove leading and trailing quotes from filenames. - - This function has been deprecated and should not be used any more: - unquoting is now taken care of by :func:`IPython.utils.process.arg_split`. - """ - warn("'unquote_filename' is deprecated since IPython 5.0 and should not " - "be used anymore", DeprecationWarning, stacklevel=2) - if win32: - if name.startswith(("'", '"')) and name.endswith(("'", '"')): - name = name[1:-1] - return name - - def compress_user(path): """Reverse of :func:`os.path.expanduser` """ @@ -89,7 +75,7 @@ def compress_user(path): path = "~" + path[len(home):] return path -def get_py_filename(name, force_win32=None): +def get_py_filename(name): """Return a valid python filename in the current directory. If the given name is not a file, it adds '.py' and searches again. @@ -97,10 +83,6 @@ def get_py_filename(name, force_win32=None): """ name = os.path.expanduser(name) - if force_win32 is not None: - warn("The 'force_win32' argument to 'get_py_filename' is deprecated " - "since IPython 5.0 and should not be used anymore", - DeprecationWarning, stacklevel=2) if not os.path.isfile(name) and not name.endswith('.py'): name += '.py' if os.path.isfile(name): @@ -253,36 +235,6 @@ def get_xdg_cache_dir(): return None -@undoc -def get_ipython_dir(): - warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) - from IPython.paths import get_ipython_dir - return get_ipython_dir() - -@undoc -def get_ipython_cache_dir(): - warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) - from IPython.paths import get_ipython_cache_dir - return get_ipython_cache_dir() - -@undoc -def get_ipython_package_dir(): - warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) - from IPython.paths import get_ipython_package_dir - return get_ipython_package_dir() - -@undoc -def get_ipython_module_path(module_str): - warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) - from IPython.paths import get_ipython_module_path - return get_ipython_module_path(module_str) - -@undoc -def locate_profile(profile='default'): - warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", DeprecationWarning, stacklevel=2) - from IPython.paths import locate_profile - return locate_profile(profile=profile) - def expand_path(s): """Expand $VARS and ~names in a string, like a shell diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 92f6883c4af..32741acdd9c 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -62,6 +62,7 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] + else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... diff --git a/setup.py b/setup.py index 4c52f47478e..3aa9be22cfb 100644 --- a/setup.py +++ b/setup.py @@ -171,7 +171,6 @@ nbconvert=["nbconvert"], ) - everything = set(chain.from_iterable(extras_require.values())) extras_require['all'] = list(sorted(everything)) From 6181503402e66032486b3768e22954264ee0278b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 15:56:33 -0800 Subject: [PATCH 1814/3726] remove unused skip decorators --- IPython/testing/decorators.py | 5 ----- IPython/utils/timing.py | 2 -- 2 files changed, 7 deletions(-) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index d2f325ba3ca..6d7d9faa88c 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -152,9 +152,6 @@ def module_not_available(module): "This test only runs under Windows") skip_if_not_linux = skipif(not sys.platform.startswith('linux'), "This test only runs under Linux") -skip_if_not_osx = skipif(sys.platform != 'darwin', - "This test only runs under OSX") - _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and os.environ.get('DISPLAY', '') == '') @@ -171,8 +168,6 @@ def module_not_available(module): skipif_not_matplotlib = skip_without('matplotlib') -skipif_not_sympy = skip_without('sympy') - # A null 'decorator', useful to make more readable code that needs to pick # between different decorators based on OS or other conditions null_deco = lambda f: f diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 32741acdd9c..16de71cf471 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -61,8 +61,6 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] - - else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... From 3add29c01317cbbe6742d21886701ee7cf02d332 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:28:08 -0800 Subject: [PATCH 1815/3726] test --- IPython/utils/timing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 16de71cf471..92f6883c4af 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -61,6 +61,7 @@ def clock2(): Similar to clock(), but return a tuple of user/system times.""" return resource.getrusage(resource.RUSAGE_SELF)[:2] + else: # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... From fc5ec101897ab1219427b214eb68fb691d4717bd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:31:37 -0800 Subject: [PATCH 1816/3726] cleanup --- .github/workflows/python-package.yml | 6 +++--- IPython/utils/timing.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 36ae8187fe2..9c2ae9c45b9 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,10 +10,10 @@ on: branches: [ master, 7.x ] jobs: - build: + formatting: runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 strategy: matrix: python-version: [3.8] @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install darker isort + pip install darker - name: Lint with darker run: | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( diff --git a/IPython/utils/timing.py b/IPython/utils/timing.py index 92f6883c4af..c20b5c9f8c6 100644 --- a/IPython/utils/timing.py +++ b/IPython/utils/timing.py @@ -66,6 +66,7 @@ def clock2(): # There is no distinction of user/system time under windows, so we just use # time.perff_counter() for everything... clocku = clocks = clock = time.perf_counter + def clock2(): """Under windows, system CPU time can't be measured. From 223cc65a41667eca57a0d57e42fdc851fa4cf86a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:42:43 -0800 Subject: [PATCH 1817/3726] misc coverage --- IPython/core/tests/test_magic.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index aa006f63594..7e95016b697 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1339,13 +1339,11 @@ def find_spec(self, fullname, path, target=None): return importlib.util.spec_from_loader(fullname, self) def get_filename(self, fullname): - if fullname != "my_tmp": - raise ImportError(f"unexpected module name '{fullname}'") + assert fullname == "my_tmp": return fullpath def get_data(self, path): - if not Path(path).samefile(fullpath): - raise OSError(f"expected path '{fullpath}', got '{path}'") + assert Path(path).samefile(fullpath): return Path(fullpath).read_text() sys.meta_path.insert(0, MyTempImporter()) From 8314f40dff8646df6a0da795c695df10c39f2ad2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 08:45:36 -0800 Subject: [PATCH 1818/3726] remove al IPython/kernel module --- IPython/kernel/__init__.py | 35 ---------------------------- IPython/kernel/__main__.py | 3 --- IPython/kernel/adapter.py | 1 - IPython/kernel/channels.py | 1 - IPython/kernel/channelsabc.py | 1 - IPython/kernel/client.py | 1 - IPython/kernel/clientabc.py | 1 - IPython/kernel/connect.py | 2 -- IPython/kernel/kernelspec.py | 1 - IPython/kernel/kernelspecapp.py | 1 - IPython/kernel/launcher.py | 1 - IPython/kernel/manager.py | 1 - IPython/kernel/managerabc.py | 1 - IPython/kernel/multikernelmanager.py | 1 - IPython/kernel/restarter.py | 1 - IPython/kernel/threaded.py | 1 - 16 files changed, 53 deletions(-) delete mode 100644 IPython/kernel/__init__.py delete mode 100644 IPython/kernel/__main__.py delete mode 100644 IPython/kernel/adapter.py delete mode 100644 IPython/kernel/channels.py delete mode 100644 IPython/kernel/channelsabc.py delete mode 100644 IPython/kernel/client.py delete mode 100644 IPython/kernel/clientabc.py delete mode 100644 IPython/kernel/connect.py delete mode 100644 IPython/kernel/kernelspec.py delete mode 100644 IPython/kernel/kernelspecapp.py delete mode 100644 IPython/kernel/launcher.py delete mode 100644 IPython/kernel/manager.py delete mode 100644 IPython/kernel/managerabc.py delete mode 100644 IPython/kernel/multikernelmanager.py delete mode 100644 IPython/kernel/restarter.py delete mode 100644 IPython/kernel/threaded.py diff --git a/IPython/kernel/__init__.py b/IPython/kernel/__init__.py deleted file mode 100644 index 70a05ed4aa5..00000000000 --- a/IPython/kernel/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.kernel imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.kernel` package has been deprecated since IPython 4.0." - "You should import from ipykernel or jupyter_client instead.", ShimWarning) - - -# zmq subdir is gone -sys.modules['IPython.kernel.zmq.session'] = ShimModule( - src='IPython.kernel.zmq.session', mirror='jupyter_client.session') -sys.modules['IPython.kernel.zmq'] = ShimModule( - src='IPython.kernel.zmq', mirror='ipykernel') - -for pkg in ('comm', 'inprocess'): - src = 'IPython.kernel.%s' % pkg - sys.modules[src] = ShimModule(src=src, mirror='ipykernel.%s' % pkg) - -for pkg in ('ioloop', 'blocking'): - src = 'IPython.kernel.%s' % pkg - sys.modules[src] = ShimModule(src=src, mirror='jupyter_client.%s' % pkg) - -# required for `from IPython.kernel import PKG` -from ipykernel import comm, inprocess -from jupyter_client import ioloop, blocking -# public API -from ipykernel.connect import * -from jupyter_client import * diff --git a/IPython/kernel/__main__.py b/IPython/kernel/__main__.py deleted file mode 100644 index d1f0cf53340..00000000000 --- a/IPython/kernel/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -if __name__ == '__main__': - from ipykernel import kernelapp as app - app.launch_new_instance() diff --git a/IPython/kernel/adapter.py b/IPython/kernel/adapter.py deleted file mode 100644 index 3b8c046b2d1..00000000000 --- a/IPython/kernel/adapter.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.adapter import * diff --git a/IPython/kernel/channels.py b/IPython/kernel/channels.py deleted file mode 100644 index 8c7fe2a0630..00000000000 --- a/IPython/kernel/channels.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.channels import * diff --git a/IPython/kernel/channelsabc.py b/IPython/kernel/channelsabc.py deleted file mode 100644 index 88944012d44..00000000000 --- a/IPython/kernel/channelsabc.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.channelsabc import * diff --git a/IPython/kernel/client.py b/IPython/kernel/client.py deleted file mode 100644 index a98690b74cc..00000000000 --- a/IPython/kernel/client.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.client import * diff --git a/IPython/kernel/clientabc.py b/IPython/kernel/clientabc.py deleted file mode 100644 index e0cf06c9420..00000000000 --- a/IPython/kernel/clientabc.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.clientabc import * diff --git a/IPython/kernel/connect.py b/IPython/kernel/connect.py deleted file mode 100644 index 5b6d40a5d34..00000000000 --- a/IPython/kernel/connect.py +++ /dev/null @@ -1,2 +0,0 @@ -from ipykernel.connect import * -from jupyter_client.connect import * diff --git a/IPython/kernel/kernelspec.py b/IPython/kernel/kernelspec.py deleted file mode 100644 index 123419b2f54..00000000000 --- a/IPython/kernel/kernelspec.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.kernelspec import * diff --git a/IPython/kernel/kernelspecapp.py b/IPython/kernel/kernelspecapp.py deleted file mode 100644 index 28cd33abd35..00000000000 --- a/IPython/kernel/kernelspecapp.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.kernelspecapp import * diff --git a/IPython/kernel/launcher.py b/IPython/kernel/launcher.py deleted file mode 100644 index 1953bc4809e..00000000000 --- a/IPython/kernel/launcher.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.launcher import * diff --git a/IPython/kernel/manager.py b/IPython/kernel/manager.py deleted file mode 100644 index c88097cff64..00000000000 --- a/IPython/kernel/manager.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.manager import * diff --git a/IPython/kernel/managerabc.py b/IPython/kernel/managerabc.py deleted file mode 100644 index 6b40827ff88..00000000000 --- a/IPython/kernel/managerabc.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.managerabc import * diff --git a/IPython/kernel/multikernelmanager.py b/IPython/kernel/multikernelmanager.py deleted file mode 100644 index ce576e27eaf..00000000000 --- a/IPython/kernel/multikernelmanager.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.multikernelmanager import * diff --git a/IPython/kernel/restarter.py b/IPython/kernel/restarter.py deleted file mode 100644 index dc24117c3ad..00000000000 --- a/IPython/kernel/restarter.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.restarter import * diff --git a/IPython/kernel/threaded.py b/IPython/kernel/threaded.py deleted file mode 100644 index 4a1072f7fe3..00000000000 --- a/IPython/kernel/threaded.py +++ /dev/null @@ -1 +0,0 @@ -from jupyter_client.threaded import * From ef2e48273c3c79634022d27c55b259e20b224503 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 8 Dec 2021 16:31:37 -0800 Subject: [PATCH 1819/3726] cleanup --- .github/workflows/python-package.yml | 6 +++--- IPython/core/tests/test_magic.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 36ae8187fe2..9c2ae9c45b9 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,10 +10,10 @@ on: branches: [ master, 7.x ] jobs: - build: + formatting: runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 strategy: matrix: python-version: [3.8] @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install darker isort + pip install darker - name: Lint with darker run: | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 7e95016b697..40f0ae47759 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -1339,11 +1339,11 @@ def find_spec(self, fullname, path, target=None): return importlib.util.spec_from_loader(fullname, self) def get_filename(self, fullname): - assert fullname == "my_tmp": + assert fullname == "my_tmp" return fullpath def get_data(self, path): - assert Path(path).samefile(fullpath): + assert Path(path).samefile(fullpath) return Path(fullpath).read_text() sys.meta_path.insert(0, MyTempImporter()) From e952cabe76360fac56ad67b484b779209474d790 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 09:48:28 -0800 Subject: [PATCH 1820/3726] remove proxy --- IPython/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/IPython/__init__.py b/IPython/__init__.py index 5df72f83775..5d656e40a25 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -140,5 +140,12 @@ def start_kernel(argv=None, **kwargs): Any other kwargs will be passed to the Application constructor, such as `config`. """ - from IPython.kernel.zmq.kernelapp import launch_new_instance + import warnings + + warnings.warn( + "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`", + DeprecationWarning, + stacklevel=2, + ) + from ipykernel.kernelapp import launch_new_instance return launch_new_instance(argv=argv, **kwargs) From 04456d7fd51741674bfbe347bc6764ac8598a627 Mon Sep 17 00:00:00 2001 From: nicolaslazo <45973144+nicolaslazo@users.noreply.github.com> Date: Thu, 9 Dec 2021 02:31:48 +0100 Subject: [PATCH 1821/3726] Expand ~ in filename arguments for %notebook, %save and %history magics --- IPython/core/magics/basic.py | 4 +++- IPython/core/magics/code.py | 1 + IPython/core/magics/history.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index b44b4a38128..f70605e0f0a 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -4,6 +4,7 @@ import argparse from logging import error import io +import os from pprint import pformat import sys from warnings import warn @@ -572,6 +573,7 @@ def notebook(self, s): For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb". """ args = magic_arguments.parse_argstring(self.notebook, s) + outfname = os.path.expanduser(args.filename) from nbformat import write, v4 @@ -585,7 +587,7 @@ def notebook(self, s): source=source )) nb = v4.new_notebook(cells=cells) - with io.open(args.filename, 'w', encoding='utf-8') as f: + with io.open(outfname, "w", encoding="utf-8") as f: write(nb, f, version=4) @magics_class diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index 1451bd56488..3f8100ef268 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -222,6 +222,7 @@ def save(self, parameter_s=''): fname, codefrom = args[0], " ".join(args[1:]) if not fname.endswith(('.py','.ipy')): fname += ext + fname = os.path.expanduser(fname) file_exists = os.path.isfile(fname) if file_exists and not force and not append: try: diff --git a/IPython/core/magics/history.py b/IPython/core/magics/history.py index ad58b33b0b3..d49259f07d8 100644 --- a/IPython/core/magics/history.py +++ b/IPython/core/magics/history.py @@ -151,6 +151,7 @@ def _format_lineno(session, line): # We don't want to close stdout at the end! close_at_end = False else: + outfname = os.path.expanduser(outfname) if os.path.exists(outfname): try: ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname) From 8ed09344f07742598b912f7c2beab78988ec2d73 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 10:06:50 -0800 Subject: [PATCH 1822/3726] remove a number of other deprecateed modules --- IPython/external/mathjax.py | 13 ------------- IPython/frontend.py | 29 ----------------------------- IPython/lib/kernel.py | 13 ------------- IPython/parallel.py | 20 -------------------- IPython/qt.py | 24 ------------------------ 5 files changed, 99 deletions(-) delete mode 100644 IPython/external/mathjax.py delete mode 100644 IPython/frontend.py delete mode 100644 IPython/lib/kernel.py delete mode 100644 IPython/parallel.py delete mode 100644 IPython/qt.py diff --git a/IPython/external/mathjax.py b/IPython/external/mathjax.py deleted file mode 100644 index 1b9b80905ba..00000000000 --- a/IPython/external/mathjax.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/python -""" -`IPython.external.mathjax` is deprecated with IPython 4.0+ - -mathjax is now install by default with the notebook package - -""" - -import sys - -if __name__ == '__main__' : - sys.exit("IPython.external.mathjax is deprecated, Mathjax is now installed by default with the notebook package") - diff --git a/IPython/frontend.py b/IPython/frontend.py deleted file mode 100644 index 9cc3eaff2f0..00000000000 --- a/IPython/frontend.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Shim to maintain backwards compatibility with old frontend imports. - -We have moved all contents of the old `frontend` subpackage into top-level -subpackages (`html`, `qt` and `terminal`), and flattened the notebook into -just `IPython.html`, formerly `IPython.frontend.html.notebook`. - -This will let code that was making `from IPython.frontend...` calls continue -working, though a warning will be printed. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The top-level `frontend` package has been deprecated since IPython 1.0. " - "All its subpackages have been moved to the top `IPython` level.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.frontend.html.notebook'] = ShimModule( - src='IPython.frontend.html.notebook', mirror='IPython.html') -sys.modules['IPython.frontend'] = ShimModule( - src='IPython.frontend', mirror='IPython') diff --git a/IPython/lib/kernel.py b/IPython/lib/kernel.py deleted file mode 100644 index af9827667fb..00000000000 --- a/IPython/lib/kernel.py +++ /dev/null @@ -1,13 +0,0 @@ -"""[DEPRECATED] Utilities for connecting to kernels - -Moved to IPython.kernel.connect -""" - -import warnings -warnings.warn("IPython.lib.kernel moved to IPython.kernel.connect in IPython 1.0," - " and will be removed in IPython 6.0.", - DeprecationWarning -) - -from ipykernel.connect import * - diff --git a/IPython/parallel.py b/IPython/parallel.py deleted file mode 100644 index 0f100127839..00000000000 --- a/IPython/parallel.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.parallel imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.parallel` package has been deprecated since IPython 4.0. " - "You should import from ipyparallel instead.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.parallel'] = ShimModule( - src='IPython.parallel', mirror='ipyparallel') - diff --git a/IPython/qt.py b/IPython/qt.py deleted file mode 100644 index 7557a3f3298..00000000000 --- a/IPython/qt.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.qt imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.qt` package has been deprecated since IPython 4.0. " - "You should import from qtconsole instead.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -_console = sys.modules['IPython.qt.console'] = ShimModule( - src='IPython.qt.console', mirror='qtconsole') - -_qt = ShimModule(src='IPython.qt', mirror='qtconsole') - -_qt.console = _console -sys.modules['IPython.qt'] = _qt From 68ad96848b73a6a2830269630ed00f86eb020a36 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 10:54:53 -0800 Subject: [PATCH 1823/3726] misc wn updates --- docs/source/whatsnew/development.rst | 47 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index c092a6a5533..960a00130e0 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -22,12 +22,24 @@ Need to be updated: pr/* -IPython 8.0 is bringing a number of new features and improvements to both the +IPython 8.0 is bringing a large number of new features and improvements to both the user of the terminal and of the kernel via Jupyter. The removal of compatibility with older version of Python is also the opportunity to do a couple of performance improvement in particular with respect to startup time. -The main change in IPython 8.0 is the integration of the ``stack_data`` package; +This release contains 250+ Pull requests, in addition to many of the features +and backports that have made it to the 7.x branch. + +We removed almost all features, arguments, functions, and modules that were +marked as deprecated between IPython 1.0 and 5.0 and before. As reminder 5.0 was +released in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in +may 2020. The few remaining deprecated features have better deprecation warnings +or errors. + +There are many change in IPython 8.0 will will try to describe subsequently, + + +The first on is the integration of the ``stack_data`` package; which provide smarter information in traceback; in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors. @@ -50,13 +62,34 @@ IPython 8.0 is capable of telling you, where the index error occurs:: return x[0][i][0] ^ + +Numfocus Small Developer Grant +------------------------------ + To prepare for Python 3.10 we have also started working on removing reliance and any dependency that is not Python 3.10 compatible; that include migrating our -test suite to Pytest, and starting to remove nose. +test suite to pytest, and starting to remove nose. This also mean that the +``iptest`` command is now gone, and all testing is via pytest. + +This was in bog part thanks the NumFOCUS Small Developer grant, we were able to +allocate 4000 to hire `Nikita Kniazev @Kojoley `__ +who did a fantastic job at updating our code base, migrating to pytest, pushing +our coverage, and fixing a large number of bugs. I highly recommend contacting +them if you need help with C++ and Python projects -We are also removing support for Python 3.6 allowing internal code to use more +You can find all relevant issues and PRs with the SDG 2021 tag: + +https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+ + +Removing support for Older Python +--------------------------------- + + +We are also removing support for Python up to 3.7 allowing internal code to use more efficient ``pathlib``, and make better use of type annotations. +IMAGE : Pathlib, pathlib everywhere. + The completer has also seen significant updates and make use of newer Jedi API offering faster and more reliable tab completion. @@ -168,6 +201,12 @@ Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload For more information please see unit test - extensions/tests/test_autoreload.py : 'test_autoload_newly_added_objects' + +Miscelanious +------------ + +Minimum supported + ======= .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. From 7d60fba8d14cd4a56f56bf68362643da2561472c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 10:57:11 -0800 Subject: [PATCH 1824/3726] misc updates --- docs/source/whatsnew/development.rst | 147 +++++++++++++++++- docs/source/whatsnew/pr/empty-hist-range.rst | 19 --- .../whatsnew/pr/hist-range-glob-feature.rst | 25 --- docs/source/whatsnew/pr/ipdb-interact.rst | 4 - .../whatsnew/pr/remove-deprecated-stuff.rst | 8 - docs/source/whatsnew/pr/sunken-brackets.rst | 7 - .../whatsnew/pr/traceback-improvements.rst | 39 ----- docs/source/whatsnew/pr/vi-prompt-strip.rst | 29 ---- 8 files changed, 144 insertions(+), 134 deletions(-) delete mode 100644 docs/source/whatsnew/pr/empty-hist-range.rst delete mode 100644 docs/source/whatsnew/pr/hist-range-glob-feature.rst delete mode 100644 docs/source/whatsnew/pr/ipdb-interact.rst delete mode 100644 docs/source/whatsnew/pr/remove-deprecated-stuff.rst delete mode 100644 docs/source/whatsnew/pr/sunken-brackets.rst delete mode 100644 docs/source/whatsnew/pr/traceback-improvements.rst delete mode 100644 docs/source/whatsnew/pr/vi-prompt-strip.rst diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 960a00130e0..27758becbb1 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -36,7 +36,7 @@ released in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in may 2020. The few remaining deprecated features have better deprecation warnings or errors. -There are many change in IPython 8.0 will will try to describe subsequently, +There are many change in IPython 8.0 will will try to describe subsequently, The first on is the integration of the ``stack_data`` package; @@ -69,7 +69,7 @@ Numfocus Small Developer Grant To prepare for Python 3.10 we have also started working on removing reliance and any dependency that is not Python 3.10 compatible; that include migrating our test suite to pytest, and starting to remove nose. This also mean that the -``iptest`` command is now gone, and all testing is via pytest. +``iptest`` command is now gone, and all testing is via pytest. This was in bog part thanks the NumFOCUS Small Developer grant, we were able to allocate 4000 to hire `Nikita Kniazev @Kojoley `__ @@ -207,7 +207,148 @@ Miscelanious Minimum supported -======= + +History Range Glob feature +========================== + +Previously, when using ``%history``, users could specify either +a range of sessions and lines, for example: + +.. code-block:: python + + ~8/1-~6/5 # see history from the first line of 8 sessions ago, + # to the fifth line of 6 sessions ago.`` + +Or users could specify a glob pattern: + +.. code-block:: python + + -g # glob ALL history for the specified pattern. + +However users could *not* specify both. + +If a user *did* specify both a range and a glob pattern, +then the glob pattern would be used (globbing *all* history) *and the range would be ignored*. + +--- + +With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history. + +Don't start a multi line cell with sunken parenthesis +----------------------------------------------------- + +From now on IPython will not ask for the next line of input when given a single +line with more closing than opening brackets. For example, this means that if +you (mis)type ']]' instead of '[]', a ``SyntaxError`` will show up, instead of +the ``...:`` prompt continuation. + +IPython shell for ipdb interact +------------------------------- + +The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``. + +Automatic Vi prompt stripping +============================= + +When pasting code into IPython, it will strip the leading prompt characters if +there are any. For example, you can paste the following code into the console - +it will still work, even though each line is prefixed with prompts (`In`, +`Out`):: + + In [1]: 2 * 2 == 4 + Out[1]: True + + In [2]: print("This still works as pasted") + + +Previously, this was not the case for the Vi-mode prompts:: + + In [1]: [ins] In [13]: 2 * 2 == 4 + ...: Out[13]: True + ...: + File "", line 1 + [ins] In [13]: 2 * 2 == 4 + ^ + SyntaxError: invalid syntax + +This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are +skipped just as the normal ``In`` would be. + +IPython shell can be started in the Vi mode using ``ipython +--TerminalInteractiveShell.editing_mode=vi`` + +Empty History Ranges +==================== + +A number of magics that take history ranges can now be used with an empty +range. These magics are: + + * ``%save`` + * ``%load`` + * ``%pastebin`` + * ``%pycat`` + +Using them this way will make them take the history of the current session up +to the point of the magic call (such that the magic itself will not be +included). + +Therefore it is now possible to save the whole history to a file using simple +``%save ``, load and edit it using ``%load`` (makes for a nice usage +when followed with :kbd:`F2`), send it to dpaste.org using ``%pastebin``, or +view the whole thing syntax-highlighted with a single ``%pycat``. + +Traceback improvements +====================== + + +UPDATE THIS IN INPUT. + +Previously, error tracebacks for errors happening in code cells were showing a hash, the one used for compiling the Python AST:: + + In [1]: def foo(): + ...: return 3 / 0 + ...: + + In [2]: foo() + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + in + ----> 1 foo() + + in foo() + 1 def foo(): + ----> 2 return 3 / 0 + 3 + + ZeroDivisionError: division by zero + +The error traceback is now correctly formatted, showing the cell number in which the error happened:: + + In [1]: def foo(): + ...: return 3 / 0 + ...: + + In [2]: foo() + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + In [2], in + ----> 1 foo() + + In [1], in foo() + 1 def foo(): + ----> 2 return 3 / 0 + + ZeroDivisionError: division by zero + +Remove Deprecated Stuff +======================= + + +We no longer need to add `extensions` to the PYTHONPATH because that is being +handled by `load_extension`. + +We are also removing Cythonmagic, sympyprinting and rmagic as they are now in +other packages and no longer need to be inside IPython. .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. diff --git a/docs/source/whatsnew/pr/empty-hist-range.rst b/docs/source/whatsnew/pr/empty-hist-range.rst deleted file mode 100644 index a36789f631d..00000000000 --- a/docs/source/whatsnew/pr/empty-hist-range.rst +++ /dev/null @@ -1,19 +0,0 @@ -Empty History Ranges -==================== - -A number of magics that take history ranges can now be used with an empty -range. These magics are: - - * ``%save`` - * ``%load`` - * ``%pastebin`` - * ``%pycat`` - -Using them this way will make them take the history of the current session up -to the point of the magic call (such that the magic itself will not be -included). - -Therefore it is now possible to save the whole history to a file using simple -``%save ``, load and edit it using ``%load`` (makes for a nice usage -when followed with :kbd:`F2`), send it to dpaste.org using ``%pastebin``, or -view the whole thing syntax-highlighted with a single ``%pycat``. diff --git a/docs/source/whatsnew/pr/hist-range-glob-feature.rst b/docs/source/whatsnew/pr/hist-range-glob-feature.rst deleted file mode 100644 index 5c38ea7c5e7..00000000000 --- a/docs/source/whatsnew/pr/hist-range-glob-feature.rst +++ /dev/null @@ -1,25 +0,0 @@ -History Range Glob feature -========================== - -Previously, when using ``%history``, users could specify either -a range of sessions and lines, for example: - -.. code-block:: python - - ~8/1-~6/5 # see history from the first line of 8 sessions ago, - # to the fifth line of 6 sessions ago.`` - -Or users could specify a glob pattern: - -.. code-block:: python - - -g # glob ALL history for the specified pattern. - -However users could *not* specify both. - -If a user *did* specify both a range and a glob pattern, -then the glob pattern would be used (globbing *all* history) *and the range would be ignored*. - ---- - -With this enhancment, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history. diff --git a/docs/source/whatsnew/pr/ipdb-interact.rst b/docs/source/whatsnew/pr/ipdb-interact.rst deleted file mode 100644 index 8783be6da0a..00000000000 --- a/docs/source/whatsnew/pr/ipdb-interact.rst +++ /dev/null @@ -1,4 +0,0 @@ -IPython shell for ipdb interact -------------------------------- - -The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``. diff --git a/docs/source/whatsnew/pr/remove-deprecated-stuff.rst b/docs/source/whatsnew/pr/remove-deprecated-stuff.rst deleted file mode 100644 index 2a948e4f2d1..00000000000 --- a/docs/source/whatsnew/pr/remove-deprecated-stuff.rst +++ /dev/null @@ -1,8 +0,0 @@ -Remove Deprecated Stuff -================================ - -We no longer need to add `extensions` to the PYTHONPATH because that is being -handled by `load_extension`. - -We are also removing Cythonmagic, sympyprinting and rmagic as they are now in -other packages and no longer need to be inside IPython. diff --git a/docs/source/whatsnew/pr/sunken-brackets.rst b/docs/source/whatsnew/pr/sunken-brackets.rst deleted file mode 100644 index f64f936ff85..00000000000 --- a/docs/source/whatsnew/pr/sunken-brackets.rst +++ /dev/null @@ -1,7 +0,0 @@ -Don't start a multiline cell with sunken parenthesis ----------------------------------------------------- - -From now on IPython will not ask for the next line of input when given a single -line with more closing than opening brackets. For example, this means that if -you (mis)type ']]' instead of '[]', a ``SyntaxError`` will show up, instead of -the ``...:`` prompt continuation. diff --git a/docs/source/whatsnew/pr/traceback-improvements.rst b/docs/source/whatsnew/pr/traceback-improvements.rst deleted file mode 100644 index 7040f585e3f..00000000000 --- a/docs/source/whatsnew/pr/traceback-improvements.rst +++ /dev/null @@ -1,39 +0,0 @@ -Traceback improvements -====================== - -Previously, error tracebacks for errors happening in code cells were showing a hash, the one used for compiling the Python AST:: - - In [1]: def foo(): - ...: return 3 / 0 - ...: - - In [2]: foo() - --------------------------------------------------------------------------- - ZeroDivisionError Traceback (most recent call last) - in - ----> 1 foo() - - in foo() - 1 def foo(): - ----> 2 return 3 / 0 - 3 - - ZeroDivisionError: division by zero - -The error traceback is now correctly formatted, showing the cell number in which the error happened:: - - In [1]: def foo(): - ...: return 3 / 0 - ...: - - In [2]: foo() - --------------------------------------------------------------------------- - ZeroDivisionError Traceback (most recent call last) - In [2], in - ----> 1 foo() - - In [1], in foo() - 1 def foo(): - ----> 2 return 3 / 0 - - ZeroDivisionError: division by zero diff --git a/docs/source/whatsnew/pr/vi-prompt-strip.rst b/docs/source/whatsnew/pr/vi-prompt-strip.rst deleted file mode 100644 index e642a4d1f83..00000000000 --- a/docs/source/whatsnew/pr/vi-prompt-strip.rst +++ /dev/null @@ -1,29 +0,0 @@ -Automatic Vi prompt stripping -============================= - -When pasting code into IPython, it will strip the leading prompt characters if -there are any. For example, you can paste the following code into the console - -it will still work, even though each line is prefixed with prompts (`In`, -`Out`):: - - In [1]: 2 * 2 == 4 - Out[1]: True - - In [2]: print("This still works as pasted") - - -Previously, this was not the case for the Vi-mode prompts:: - - In [1]: [ins] In [13]: 2 * 2 == 4 - ...: Out[13]: True - ...: - File "", line 1 - [ins] In [13]: 2 * 2 == 4 - ^ - SyntaxError: invalid syntax - -This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are -skipped just as the normal ``In`` would be. - -IPython shell can be started in the Vi mode using ``ipython ---TerminalInteractiveShell.editing_mode=vi`` From a31b1a133c52c03b3946555954ad20178b4364cc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 11:03:34 -0800 Subject: [PATCH 1825/3726] fix complete docstrings --- IPython/core/completer.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/IPython/core/completer.py b/IPython/core/completer.py index cc7b629e2e1..2f0ad6b6bb0 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -462,7 +462,7 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC: seen.add(new_text) -def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: +def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: """ Rectify a set of completions to all have the same ``start`` and ``end`` @@ -479,6 +479,8 @@ def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: text that should be completed. completions : Iterator[Completion] iterator over the completions to rectify + _debug : bool + Log failed completion Notes ----- @@ -1128,16 +1130,23 @@ def __init__( secondary optional dict for completions, to handle cases (such as IPython embedded inside functions) where both Python scopes are visible. - use_readline : bool, optional - DEPRECATED, ignored since IPython 6.0, will have no effects + config : Config + traitlet's config object + **kwargs + passed to super class unmodified. """ self.magic_escape = ESC_MAGIC self.splitter = CompletionSplitter() # _greedy_changed() depends on splitter and readline being defined: - Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, - config=config, **kwargs) + super().__init__( + self, + namespace=namespace, + global_namespace=global_namespace, + config=config, + **kwargs + ) # List where completion matches will be stored self.matches = [] @@ -2055,9 +2064,9 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, Parameters ---------- - cursor_line : + cursor_line Index of the line the cursor is on. 0 indexed. - cursor_pos : + cursor_pos Position of the cursor in the current line/line_buffer/text. 0 indexed. line_buffer : optional, str From 33f5b346f6df04d5dc243d3e2008b75294ddf138 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 11:08:12 -0800 Subject: [PATCH 1826/3726] some docstring reformatting and fixing --- IPython/core/crashhandler.py | 20 +++++++++++-- IPython/core/getipython.py | 2 +- IPython/core/magic.py | 58 ++++++++++++++---------------------- IPython/core/page.py | 8 ++--- IPython/core/payloadpage.py | 7 ++--- IPython/core/pylabtools.py | 7 ++--- IPython/core/ultratb.py | 2 +- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/IPython/core/crashhandler.py b/IPython/core/crashhandler.py index 181fc157954..d0d4b0d7725 100644 --- a/IPython/core/crashhandler.py +++ b/IPython/core/crashhandler.py @@ -32,6 +32,8 @@ from IPython.core.release import __version__ as version +from typing import Optional + #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- @@ -95,8 +97,15 @@ def __call__(self, etype, evalue, etb) message_template = _default_message_template section_sep = '\n\n'+'*'*75+'\n\n' - def __init__(self, app, contact_name=None, contact_email=None, - bug_tracker=None, show_crash_traceback=True, call_pdb=False): + def __init__( + self, + app, + contact_name: Optional[str] = None, + contact_email: Optional[str] = None, + bug_tracker: Optional[str] = None, + show_crash_traceback: bool = True, + call_pdb: bool = False, + ): """Create a new crash handler Parameters @@ -113,10 +122,15 @@ def __init__(self, app, contact_name=None, contact_email=None, show_crash_traceback : bool If false, don't print the crash traceback on stderr, only generate the on-disk report - Non-argument instance attributes + call_pdb + Whether to call pdb on crash + + Attributes + ---------- These instances contain some non-argument attributes which allow for further customization of the crash handler's behavior. Please see the source for further details. + """ self.crash_report_fname = "Crash_report_%s.txt" % app.name self.app = app diff --git a/IPython/core/getipython.py b/IPython/core/getipython.py index e6d8a4c91d7..5e9b13cf3c6 100644 --- a/IPython/core/getipython.py +++ b/IPython/core/getipython.py @@ -16,7 +16,7 @@ def get_ipython(): """Get the global InteractiveShell instance. - + Returns None if no InteractiveShell instance is registered. """ from IPython.core.interactiveshell import InteractiveShell diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 7f7f1f94b28..3dc3480b561 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -114,16 +114,13 @@ def record_magic(dct, magic_kind, magic_name, func): Parameters ---------- dct : dict - A dictionary with 'line' and 'cell' subdicts. - + A dictionary with 'line' and 'cell' subdicts. magic_kind : str - Kind of magic to be stored. - + Kind of magic to be stored. magic_name : str - Key to store the magic as. - + Key to store the magic as. func : function - Callable object to store. + Callable object to store. """ if magic_kind == 'line_cell': dct['line'][magic_name] = dct['cell'][magic_name] = func @@ -372,7 +369,7 @@ def lsmagic_docs(self, brief=False, missing=''): def register(self, *magic_objects): """Register one or more instances of Magics. - Take one or more classes or instances of classes that subclass the main + Take one or more classes or instances of classes that subclass the main `core.Magic` class, and register them with IPython to use the magic functions they provide. The registration process will then ensure that any methods that have decorated to provide line and/or cell magics will @@ -387,7 +384,7 @@ def register(self, *magic_objects): Parameters ---------- - magic_objects : one or more classes or instances + *magic_objects : one or more classes or instances """ # Start by validating them to ensure they have all had their magic # methods registered at the instance level @@ -410,7 +407,7 @@ def register_function(self, func, magic_kind='line', magic_name=None): This will create an IPython magic (line, cell or both) from a standalone function. The functions should have the following - signatures: + signatures: * For line magics: `def f(line)` * For cell magics: `def f(line, cell)` @@ -422,14 +419,12 @@ def register_function(self, func, magic_kind='line', magic_name=None): Parameters ---------- func : callable - Function to be registered as a magic. - + Function to be registered as a magic. magic_kind : str - Kind of magic, one of 'line', 'cell' or 'line_cell' - + Kind of magic, one of 'line', 'cell' or 'line_cell' magic_name : optional str - If given, the name the magic will have in the IPython namespace. By - default, the name of the function itself is used. + If given, the name the magic will have in the IPython namespace. By + default, the name of the function itself is used. """ # Create the new method in the user_magics and register it in the @@ -450,13 +445,11 @@ def register_alias(self, alias_name, magic_name, magic_kind='line', magic_params Parameters ---------- alias_name : str - The name of the magic to be registered. - + The name of the magic to be registered. magic_name : str - The name of an existing magic. - + The name of an existing magic. magic_kind : str - Kind of magic, one of 'line' or 'cell' + Kind of magic, one of 'line' or 'cell' """ # `validate_type` is too permissive, as it allows 'line_cell' @@ -580,25 +573,20 @@ def parse_options(self, arg_str, opt_str, *long_opts, **kw): Parameters ---------- - arg_str : str - The arguments to parse. - + The arguments to parse. opt_str : str - The options specification. - + The options specification. mode : str, default 'string' - If given as 'list', the argument string is returned as a list (split - on whitespace) instead of a string. - + If given as 'list', the argument string is returned as a list (split + on whitespace) instead of a string. list_all : bool, default False - Put all option values in lists. Normally only options - appearing more than once are put in a list. - + Put all option values in lists. Normally only options + appearing more than once are put in a list. posix : bool, default True - Whether to split the input line in POSIX mode or not, as per the - conventions outlined in the :mod:`shlex` module from the standard - library. + Whether to split the input line in POSIX mode or not, as per the + conventions outlined in the :mod:`shlex` module from the standard + library. """ # inject default options at the beginning of the input line diff --git a/IPython/core/page.py b/IPython/core/page.py index 44903255ec2..24770c56a3c 100644 --- a/IPython/core/page.py +++ b/IPython/core/page.py @@ -46,7 +46,7 @@ def display_page(strng, start=0, screen_lines=25): def as_hook(page_func): """Wrap a pager func to strip the `self` arg - + so it can be called as a hook. """ return lambda self, *args, **kwargs: page_func(*args, **kwargs) @@ -127,7 +127,7 @@ def _detect_screen_size(screen_lines_def): def pager_page(strng, start=0, screen_lines=0, pager_cmd=None): """Display a string, piping through a pager after a certain length. - + strng can be a mime-bundle dict, supplying multiple representations, keyed by mime-type. @@ -238,10 +238,10 @@ def pager_page(strng, start=0, screen_lines=0, pager_cmd=None): def page(data, start=0, screen_lines=0, pager_cmd=None): """Display content in a pager, piping through a pager after a certain length. - + data can be a mime-bundle dict, supplying multiple representations, keyed by mime-type, or text. - + Pager is dispatched via the `show_in_pager` IPython hook. If no hook is registered, `pager_page` will be used. """ diff --git a/IPython/core/payloadpage.py b/IPython/core/payloadpage.py index eb613445dd4..4958108076b 100644 --- a/IPython/core/payloadpage.py +++ b/IPython/core/payloadpage.py @@ -17,10 +17,9 @@ def page(strng, start=0, screen_lines=0, pager_cmd=None): Parameters ---------- strng : str or mime-dict - Text to page, or a mime-type keyed dict of already formatted data. - + Text to page, or a mime-type keyed dict of already formatted data. start : int - Starting line at which to place the display. + Starting line at which to place the display. """ # Some routines may auto-compute start offsets incorrectly and pass a @@ -42,7 +41,7 @@ def page(strng, start=0, screen_lines=0, pager_cmd=None): def install_payload_page(): """DEPRECATED, use show_in_pager hook - + Install this version of page as IPython.core.page.page. """ warnings.warn("""install_payload_page is deprecated. diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 711ee758e77..68e100f7d07 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -185,8 +185,8 @@ def mpl_runner(safe_execfile): Parameters ---------- safe_execfile : function - This must be a function with the same interface as the - :meth:`safe_execfile` method of IPython. + This must be a function with the same interface as the + :meth:`safe_execfile` method of IPython. Returns ------- @@ -241,7 +241,7 @@ def select_figure_formats(shell, formats, **kwargs): """Select figure formats for the inline backend. Parameters - ========== + ---------- shell : InteractiveShell The main IPython instance. formats : str or set @@ -408,7 +408,6 @@ def configure_inline_support(shell, backend): Parameters ---------- shell : InteractiveShell instance - backend : matplotlib backend """ warnings.warn( diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index c7ce562524b..5c62ee91485 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -556,7 +556,7 @@ def get_exception_only(self, etype, value): Parameters ---------- etype : exception type - evalue : exception value + value : exception value """ return ListTB.structured_traceback(self, etype, value) From d3083dd39cc61c72ba45b9af04cbad6b049426ac Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 11:26:35 -0800 Subject: [PATCH 1827/3726] some manual fixes --- IPython/core/interactiveshell.py | 20 +++++++++----------- IPython/core/oinspect.py | 32 ++++++++++++++------------------ 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 020e892f543..f45ffeec4a4 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2057,16 +2057,14 @@ def complete(self, text, line=None, cursor_pos=None): Parameters ---------- - text : string - A string of text to be completed on. It can be given as empty and - instead a line/position pair are given. In this case, the - completer itself will split the line like readline does. - - line : string, optional - The complete line that text is part of. - - cursor_pos : int, optional - The position of the cursor on the input line. + text : string + A string of text to be completed on. It can be given as empty and + instead a line/position pair are given. In this case, the + completer itself will split the line like readline does. + line : string, optional + The complete line that text is part of. + cursor_pos : int, optional + The position of the cursor on the input line. Returns ------- @@ -2606,7 +2604,7 @@ def safe_execfile(self, fname, *where, exit_ignore=False, raise_exceptions=False ---------- fname : string The name of the file to be executed. - where : tuple + *where : tuple One or two namespaces, passed to execfile() as (globals,locals). If only one is given, it is passed as both. exit_ignore : bool (False) diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 9e52f1e4771..db2bd57892d 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -302,7 +302,7 @@ def find_file(obj) -> str: Returns ------- fname : str - The absolute path to the file where the object was defined. + The absolute path to the file where the object was defined. """ obj = _get_wrapped(obj) @@ -337,7 +337,7 @@ def find_source_lines(obj): Returns ------- lineno : int - The line number where the object definition starts. + The line number where the object definition starts. """ obj = _get_wrapped(obj) @@ -428,7 +428,6 @@ def pdoc(self, obj, oname='', formatter=None): Examples -------- - In [1]: class NoInit: ...: pass @@ -575,18 +574,17 @@ def _get_info( """Retrieve an info dict and format it. Parameters - ========== - - obj: any + ---------- + obj : any Object to inspect and return info from oname: str (default: ''): Name of the variable pointing to `obj`. - formatter: callable + formatter : callable info: already computed information - detail_level: integer + detail_level : integer Granularity of detail level, if set to 1, give more information. - omit_sections: container[str] + omit_sections : container[str] Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`) """ @@ -716,21 +714,19 @@ def _info(self, obj, oname='', info=None, detail_level=0) -> dict: """Compute a dict with detailed information about an object. Parameters - ========== - - obj: any + ---------- + obj : any An object to find information about - oname: str (default: ''): + oname : str (default: '') Name of the variable pointing to `obj`. - info: (default: None) + info : (default: None) A struct (dict like with attr access) with some information fields which may have been precomputed already. - detail_level: int (default:0) + detail_level : int (default:0) If set to 1, more information is given. Returns - ======= - + ------- An object info dict with known fields from `info_fields`. Keys are strings, values are string or None. """ @@ -962,7 +958,7 @@ def psearch(self,pattern,ns_table,ns_search=[], - show_all(False): show all names, including those starting with underscores. - + - list_types(False): list all available object types for object matching. """ #print 'ps pattern:<%r>' % pattern # dbg From af9d424266bf61e3c8db7ab2e1228dc206466452 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 11:27:43 -0800 Subject: [PATCH 1828/3726] reformat all of core --- IPython/core/debugger.py | 3 - IPython/core/display.py | 18 +++- IPython/core/formatters.py | 3 + IPython/core/history.py | 96 +++++++++---------- IPython/core/inputsplitter.py | 52 +++++----- IPython/core/inputtransformer.py | 17 ++-- IPython/core/inputtransformer2.py | 12 +-- IPython/core/interactiveshell.py | 151 ++++++++++++++---------------- 8 files changed, 176 insertions(+), 176 deletions(-) diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d3e4ec2defe..8e3dd9678cd 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -798,7 +798,6 @@ def do_where(self, arg): def break_anywhere(self, frame): """ - _stop_in_decorator_internals is overly restrictive, as we may still want to trace function calls, so we need to also update break_anywhere so that is we don't `stop_here`, because of debugger skip, we may still @@ -820,8 +819,6 @@ def _is_in_decorator_internal_and_should_skip(self, frame): """ Utility to tell us whether we are in a decorator internal and should stop. - - """ # if we are disabled don't skip diff --git a/IPython/core/display.py b/IPython/core/display.py index 310e2fa2161..f3934c2d9d7 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -826,15 +826,19 @@ def __init__( data : unicode, str or bytes The raw image data or a URL or filename to load the data from. This always results in embedded image data. + url : unicode A URL to download the data from. If you specify `url=`, the image data will not be embedded unless you also specify `embed=True`. + filename : unicode Path to a local file to load the data from. Images from a file are always embedded. + format : unicode The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given for format will be inferred from the filename extension. + embed : bool Should the image data be embedded using a data URI (True) or be loaded using an tag. Set this to True if you want the image @@ -844,10 +848,13 @@ def __init__( default value is `False`. Note that QtConsole is not able to display images if `embed` is set to `False` + width : int Width in pixels to which to constrain the image in html + height : int Height in pixels to which to constrain the image in html + retina : bool Automatically set the width and height to half of the measured width and height. @@ -855,10 +862,13 @@ def __init__( from image data. For non-embedded images, you can just set the desired display width and height directly. + unconfined : bool Set unconfined=True to disable max-width confinement of the image. + metadata : dict Specify extra metadata to attach to the image. + alt : unicode Alternative text for the image, for use by screen readers. @@ -1067,12 +1077,15 @@ def __init__(self, data=None, url=None, filename=None, embed=False, data : unicode, str or bytes The raw video data or a URL or filename to load the data from. Raw data will require passing ``embed=True``. + url : unicode A URL for the video. If you specify ``url=``, the image data will not be embedded. + filename : unicode Path to a local file containing the video. Will be interpreted as a local URL unless ``embed=True``. + embed : bool Should the video be embedded using a data URI (True) or be loaded using a