From 127b8bf62db3a159293884cb45e6b02c8d0f5ba8 Mon Sep 17 00:00:00 2001 From: Niclas Date: Sun, 10 Mar 2019 21:00:31 +0100 Subject: [PATCH 001/370] allow type of already existing class instances to be updated --- IPython/extensions/autoreload.py | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 4edd3b3a97d..7bb5a5422ab 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -115,6 +115,7 @@ import traceback import types import weakref +import inspect from importlib import import_module from importlib.util import source_from_cache from imp import reload @@ -267,6 +268,54 @@ def update_function(old, new): pass +def update_instances(old, new, objects=None): + """Iterate through objects recursively, searching for instances of old and + replace their __class__ reference with new. If no objects are given, start + with the current ipython workspace. + """ + if not objects: + # find ipython workspace stack frame + frame = next(frame_nfo.frame for frame_nfo in inspect.stack() + if 'trigger' in frame_nfo.function) + # build generator for non-private variable values from workspace + shell = frame.f_locals['self'].shell + user_ns = shell.user_ns + user_ns_hidden = shell.user_ns_hidden + nonmatching = object() + objects = ( value for key, value in user_ns.items() + if not key.startswith('_') + and (value is not user_ns_hidden.get(key, nonmatching)) + and not inspect.ismodule(value)) + + # use dict values if objects is a dict but don't touch private variables + if hasattr(objects, 'items'): + objects = (value for key, value in objects.items() + if not str(key).startswith('_') + and not inspect.ismodule(value) ) + + # try if objects is iterable + try: + for obj in objects: + + # update, if object is instance of old_class (but no subclasses) + if type(obj) is old: + obj.__class__ = new + + + # if object is instance of other class, look for nested instances + if hasattr(obj, '__dict__') and not (inspect.isfunction(obj) + or inspect.ismethod(obj)): + update_instances(old, new, obj.__dict__) + + # if object is a container, search it + if hasattr(obj, 'items') or (hasattr(obj, '__contains__') + and not isinstance(obj, str)): + update_instances(old, new, obj) + + except TypeError: + pass + + def update_class(old, new): """Replace stuff in the __dict__ of a class, and upgrade method code objects, and add new methods, if any""" @@ -300,6 +349,9 @@ def update_class(old, new): except (AttributeError, TypeError): pass # skip non-writable attributes + # update all instances of class + update_instances(old, new) + def update_property(old, new): """Replace get/set/del functions of a property""" From f3c5ecdf9d43f46eb51d2d5d159a69bd85376625 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 19 Mar 2019 13:58:00 -0700 Subject: [PATCH 002/370] Try to fix updating classes in Autoreload. There seem to have been some infinite recursion in the previous version of the code, so implement a more classical graph finding algorithm. This should still be properly tested --- IPython/extensions/autoreload.py | 58 ++++++++++++++++++++- IPython/extensions/tests/test_autoreload.py | 4 +- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 7bb5a5422ab..5bd38b3b2dc 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -268,6 +268,60 @@ def update_function(old, new): pass +def _find_instances(old_type): + """Try to find all instances of a class that need updating. + + Classic graph exploration, we want to avoid re-visiting object multiple times. + """ + # find ipython workspace stack frame, this is just to bootstrap where we + # find the object that need updating. + frame = next(frame_nfo.frame for frame_nfo in inspect.stack() + if 'trigger' in frame_nfo.function) + # build generator for non-private variable values from workspace + shell = frame.f_locals['self'].shell + user_ns = shell.user_ns + user_ns_hidden = shell.user_ns_hidden + nonmatching = object() + objects = ( value for key, value in user_ns.items() + if not key.startswith('_') + and (value is not user_ns_hidden.get(key, nonmatching)) + and not inspect.ismodule(value)) + + # note: in the following we do use dict as object might not be hashable. + # list of objects we found that will need an update. + to_update = {} + + # list of object we have not recursed into yet + open_set = {} + + # list of object we have visited already + closed_set = {} + + open_set.update({id(o):o for o in objects}) + + it = 0 + while len(open_set) > 0: + it += 1 + if it > 100_000: + raise ValueError('infinite') + (current_id,current) = next(iter(open_set.items())) + if type(current) is old_type: + to_update[current_id] = current + if hasattr(current, '__dict__') and not (inspect.isfunction(current) + or inspect.ismethod(current)): + potential_new = {id(o):o for o in current.__dict__.values() if id(o) not in closed_set.keys()} + open_set.update(potential_new) + # if object is a container, search it + if hasattr(current, 'items') or (hasattr(current, '__contains__') + and not isinstance(current, str)): + potential_new = (value for key, value in current.items() + if not str(key).startswith('_') + and not inspect.ismodule(value) and not id(value) in closed_set.keys()) + open_set.update(potential_new) + del open_set[id(current)] + closed_set[id(current)] = current + return to_update.values() + def update_instances(old, new, objects=None): """Iterate through objects recursively, searching for instances of old and replace their __class__ reference with new. If no objects are given, start @@ -319,6 +373,7 @@ def update_instances(old, new, objects=None): def update_class(old, new): """Replace stuff in the __dict__ of a class, and upgrade method code objects, and add new methods, if any""" + print('old is', old) for key in list(old.__dict__.keys()): old_obj = getattr(old, key) try: @@ -350,7 +405,8 @@ def update_class(old, new): pass # skip non-writable attributes # update all instances of class - update_instances(old, new) + for instance in _find_instances(old): + instance.__class__ = new def update_property(old, new): diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py index 74e01256bda..40d63ffbf8e 100644 --- a/IPython/extensions/tests/test_autoreload.py +++ b/IPython/extensions/tests/test_autoreload.py @@ -35,10 +35,12 @@ noop = lambda *a, **kw: None -class FakeShell(object): +class FakeShell: def __init__(self): self.ns = {} + self.user_ns = {} + self.user_ns_hidden = {} self.events = EventManager(self, {'pre_run_cell', pre_run_cell}) self.auto_magics = AutoreloadMagics(shell=self) self.events.register('pre_run_cell', self.auto_magics.pre_run_cell) From 386ca8cdde295b91a9106e8f3ecbc4882c7e5e51 Mon Sep 17 00:00:00 2001 From: Akshay Paropkari Date: Thu, 21 Mar 2019 14:16:49 -0700 Subject: [PATCH 003/370] 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 2071cef1ee0..62e260050a8 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 = 4 +_version_minor = 5 _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 fa4bd6d15087f3881f3e557da2a45849ed621982 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 22 Mar 2019 09:49:17 -0700 Subject: [PATCH 004/370] typo --- docs/source/whatsnew/version7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whatsnew/version7.rst b/docs/source/whatsnew/version7.rst index 5efdacaa098..f36634caa70 100644 --- a/docs/source/whatsnew/version7.rst +++ b/docs/source/whatsnew/version7.rst @@ -42,7 +42,7 @@ Miscelanious - Fix improper acceptation of ``return`` outside of functions. :ghpull:`11641`. - Fixed PyQt 5.11 backwards incompatibility causing sip import failure. :ghpull:`11613`. - - Fix Bug where ``type?`` woudl crash IPython. :ghpull:`1608`. + - Fix Bug where ``type?`` would crash IPython. :ghpull:`1608`. - Allow to apply ``@needs_local_scope`` to cell magics for convenience. :ghpull:`11542`. From 560d4dce0a1a7e579ae1c7d2eb5a779e1b4e5b6c Mon Sep 17 00:00:00 2001 From: Nick Tallant Date: Fri, 22 Mar 2019 21:00:09 -0500 Subject: [PATCH 005/370] Verbose error message when displaying an Image with a data path that does not exist --- IPython/core/display.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index db75659e97d..22cfaebd643 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1250,7 +1250,11 @@ def _repr_mimebundle_(self, include=None, exclude=None): def _data_and_metadata(self, always_both=False): """shortcut for returning metadata with shape information, if defined""" - b64_data = b2a_base64(self.data).decode('ascii') + try: + b64_data = b2a_base64(self.data).decode('ascii') + except TypeError: + raise FileNotFoundError( + "No such file or directory: '%s'" % (self.data)) md = {} if self.metadata: md.update(self.metadata) @@ -1266,12 +1270,10 @@ def _data_and_metadata(self, always_both=False): return b64_data def _repr_png_(self): - if self.embed and self.format == self._FMT_PNG: - return self._data_and_metadata() + return self._data_and_metadata() def _repr_jpeg_(self): - if self.embed and self.format == self._FMT_JPEG: - return self._data_and_metadata() + return self._data_and_metadata() def _find_ext(self, s): return s.split('.')[-1].lower() From c841e6d78a398e81fda89bc5ddc661112297c5a0 Mon Sep 17 00:00:00 2001 From: Nick Tallant Date: Fri, 22 Mar 2019 21:06:19 -0500 Subject: [PATCH 006/370] Restoring two lines I accidentally removed in display.py --- IPython/core/display.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 22cfaebd643..0957623570a 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1270,10 +1270,12 @@ def _data_and_metadata(self, always_both=False): return b64_data def _repr_png_(self): - return self._data_and_metadata() + if self.embed and self.format == self._FMT_PNG: + return self._data_and_metadata() def _repr_jpeg_(self): - return self._data_and_metadata() + if self.embed and self.format == self._FMT_JPEG: + return self._data_and_metadata() def _find_ext(self, s): return s.split('.')[-1].lower() From d05065c04115748d7aa9cbe5540970147e8c55d4 Mon Sep 17 00:00:00 2001 From: Niclas Date: Mon, 1 Apr 2019 01:13:47 +0200 Subject: [PATCH 007/370] Combined recursive approach with check for already visited objects to avoid infinite recursion. This should pass the already existing autoreload tests, however, new tests for this feature still need to be implemented. --- IPython/extensions/autoreload.py | 75 +++++--------------------------- 1 file changed, 12 insertions(+), 63 deletions(-) diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 5bd38b3b2dc..353124ae855 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -268,66 +268,14 @@ def update_function(old, new): pass -def _find_instances(old_type): - """Try to find all instances of a class that need updating. - - Classic graph exploration, we want to avoid re-visiting object multiple times. - """ - # find ipython workspace stack frame, this is just to bootstrap where we - # find the object that need updating. - frame = next(frame_nfo.frame for frame_nfo in inspect.stack() - if 'trigger' in frame_nfo.function) - # build generator for non-private variable values from workspace - shell = frame.f_locals['self'].shell - user_ns = shell.user_ns - user_ns_hidden = shell.user_ns_hidden - nonmatching = object() - objects = ( value for key, value in user_ns.items() - if not key.startswith('_') - and (value is not user_ns_hidden.get(key, nonmatching)) - and not inspect.ismodule(value)) - - # note: in the following we do use dict as object might not be hashable. - # list of objects we found that will need an update. - to_update = {} - - # list of object we have not recursed into yet - open_set = {} - - # list of object we have visited already - closed_set = {} - - open_set.update({id(o):o for o in objects}) - - it = 0 - while len(open_set) > 0: - it += 1 - if it > 100_000: - raise ValueError('infinite') - (current_id,current) = next(iter(open_set.items())) - if type(current) is old_type: - to_update[current_id] = current - if hasattr(current, '__dict__') and not (inspect.isfunction(current) - or inspect.ismethod(current)): - potential_new = {id(o):o for o in current.__dict__.values() if id(o) not in closed_set.keys()} - open_set.update(potential_new) - # if object is a container, search it - if hasattr(current, 'items') or (hasattr(current, '__contains__') - and not isinstance(current, str)): - potential_new = (value for key, value in current.items() - if not str(key).startswith('_') - and not inspect.ismodule(value) and not id(value) in closed_set.keys()) - open_set.update(potential_new) - del open_set[id(current)] - closed_set[id(current)] = current - return to_update.values() - -def update_instances(old, new, objects=None): +def update_instances(old, new, objects=None, visited={}): """Iterate through objects recursively, searching for instances of old and replace their __class__ reference with new. If no objects are given, start with the current ipython workspace. """ - if not objects: + if objects is None: + # make sure visited is cleaned when not called recursively + visited = {} # find ipython workspace stack frame frame = next(frame_nfo.frame for frame_nfo in inspect.stack() if 'trigger' in frame_nfo.function) @@ -349,7 +297,9 @@ def update_instances(old, new, objects=None): # try if objects is iterable try: - for obj in objects: + for obj in (obj for obj in objects if id(obj) not in visited): + # add current object to visited to avoid revisiting + visited.update({id(obj):obj}) # update, if object is instance of old_class (but no subclasses) if type(obj) is old: @@ -359,21 +309,21 @@ def update_instances(old, new, objects=None): # if object is instance of other class, look for nested instances if hasattr(obj, '__dict__') and not (inspect.isfunction(obj) or inspect.ismethod(obj)): - update_instances(old, new, obj.__dict__) + update_instances(old, new, obj.__dict__, visited) # if object is a container, search it if hasattr(obj, 'items') or (hasattr(obj, '__contains__') and not isinstance(obj, str)): - update_instances(old, new, obj) + update_instances(old, new, obj, visited) except TypeError: pass - + def update_class(old, new): """Replace stuff in the __dict__ of a class, and upgrade method code objects, and add new methods, if any""" - print('old is', old) + print('old is', id(old)) for key in list(old.__dict__.keys()): old_obj = getattr(old, key) try: @@ -405,8 +355,7 @@ def update_class(old, new): pass # skip non-writable attributes # update all instances of class - for instance in _find_instances(old): - instance.__class__ = new + update_instances(old, new) def update_property(old, new): From a062bb7bbe4d6826269e474bcd738f629876fed4 Mon Sep 17 00:00:00 2001 From: stef-ubuntu Date: Wed, 3 Apr 2019 16:09:44 +0900 Subject: [PATCH 008/370] Add element_id to Audio widget for javascript interaction --- IPython/lib/display.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/IPython/lib/display.py b/IPython/lib/display.py index fe66f4f2bf6..6caef9a3278 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -89,7 +89,8 @@ class Audio(DisplayObject): """ _read_flags = 'rb' - def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True): + def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, + element_id=None): if filename is None and url is None and data is None: raise ValueError("No audio data found. Expecting filename, url, or data.") if embed is False and url is None: @@ -100,6 +101,7 @@ def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, au else: self.embed = True self.autoplay = autoplay + self.element_id = element_id super(Audio, self).__init__(data=data, url=url, filename=filename) if self.data is not None and not isinstance(self.data, bytes): @@ -198,12 +200,13 @@ def _data_and_metadata(self): def _repr_html_(self): src = """ -