Skip to content

Commit

Permalink
fix: make .widget and .widget_types deprecated
Browse files Browse the repository at this point in the history
In jupyter-widgets#3122 we renamed .widget and .widget_types to ._active_widgets
and ._widget_types. That breaks code, and we did not have a deprecation
period.
This PR makes the dict and registry non-members of the Widget class
and puts in a backwards compatible way the deprecated these members.
  • Loading branch information
maartenbreddels committed Aug 24, 2022
1 parent 74774da commit ca91f89
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 18 deletions.
5 changes: 3 additions & 2 deletions python/ipywidgets/ipywidgets/embed.py
Expand Up @@ -12,6 +12,7 @@

import json
import re
import ipywidgets.widgets.widget
from .widgets import Widget, DOMWidget
from .widgets.widget_link import Link
from .widgets.docutils import doc_subst
Expand Down Expand Up @@ -129,7 +130,7 @@ def _get_recursive_state(widget, store=None, drop_defaults=False):

def add_resolved_links(store, drop_defaults):
"""Adds the state of any link models between two models in store"""
for widget_id, widget in Widget._active_widgets.items(): # go over all widgets
for widget_id, widget in ipywidgets.widgets.widget.instances.items(): # go over all widgets
if isinstance(widget, Link) and widget_id not in store:
if widget.source[0].model_id in store and widget.target[0].model_id in store:
store[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
Expand Down Expand Up @@ -207,7 +208,7 @@ def embed_data(views, drop_defaults=True, state=None):
view_specs: a list of widget view specs
"""
if views is None:
views = [w for w in Widget._active_widgets.values() if isinstance(w, DOMWidget)]
views = [w for w in ipywidgets.widgets.widget.instances.values() if isinstance(w, DOMWidget)]
else:
try:
views[0]
Expand Down
3 changes: 2 additions & 1 deletion python/ipywidgets/ipywidgets/tests/test_embed.py
Expand Up @@ -9,6 +9,7 @@

import traitlets

import ipywidgets.widgets.widget
from ..widgets import IntSlider, IntText, Text, Widget, jslink, HBox, widget_serialization
from ..embed import embed_data, embed_snippet, embed_minimal_html, dependency_state

Expand All @@ -29,7 +30,7 @@ class CaseWidget(Widget):
class TestEmbed:

def teardown(self):
for w in tuple(Widget._active_widgets.values()):
for w in tuple(ipywidgets.widgets.widget.instances.values()):
w.close()

def test_embed_data_simple(self):
Expand Down
18 changes: 16 additions & 2 deletions python/ipywidgets/ipywidgets/widgets/tests/test_widget.py
Expand Up @@ -7,6 +7,7 @@
from IPython.display import display
from IPython.utils.capture import capture_output

from .. import widget
from ..widget import Widget
from ..widget_button import Button

Expand Down Expand Up @@ -49,9 +50,22 @@ def test_close_all():
# create a couple of widgets
widgets = [Button() for i in range(10)]

assert len(Widget._active_widgets) > 0, "expect active widgets"
assert len(widget.instances) > 0, "expect active widgets"

# close all the widgets
Widget.close_all()

assert len(Widget._active_widgets) == 0, "active widgets should be cleared"
assert len(widget.instances) == 0, "active widgets should be cleared"


def test_compatibility():
button = Button()
assert button in widget.Widget.widgets.values()
assert widget.instances is widget.Widget.widgets
assert widget.instances is widget.Widget._active_widgets
Widget.close_all()
assert not widget.Widget.widgets
assert not widget.Widget._active_widgets

assert widget.Widget.widget_types is widget.registry
assert widget.Widget._widget_types is widget.registry
61 changes: 48 additions & 13 deletions python/ipywidgets/ipywidgets/widgets/widget.py
Expand Up @@ -6,8 +6,10 @@
in the Jupyter notebook front-end.
"""
import os
import typing
from contextlib import contextmanager
from collections.abc import Iterable
import warnings
from IPython import get_ipython
from ipykernel.comm import Comm
from traitlets import (
Expand All @@ -34,6 +36,9 @@ def envset(name, default):
PROTOCOL_VERSION_MAJOR = __protocol_version__.split('.')[0]
CONTROL_PROTOCOL_VERSION_MAJOR = __control_protocol_version__.split('.')[0]
JUPYTER_WIDGETS_ECHO = envset('JUPYTER_WIDGETS_ECHO', default=True)
# we keep a strong reference for every widget created, for a discussion on using weak references see:
# https://github.com/jupyter-widgets/ipywidgets/issues/1345
instances : typing.MutableMapping[str, "Widget"] = {}

def _widget_to_json(x, obj):
if isinstance(x, dict):
Expand All @@ -50,8 +55,8 @@ def _json_to_widget(x, obj):
return {k: _json_to_widget(v, obj) for k, v in x.items()}
elif isinstance(x, (list, tuple)):
return [_json_to_widget(v, obj) for v in x]
elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in Widget._active_widgets:
return Widget._active_widgets[x[10:]]
elif isinstance(x, str) and x.startswith('IPY_MODEL_') and x[10:] in instances:
return instances[x[10:]]
else:
return x

Expand Down Expand Up @@ -259,10 +264,16 @@ def items(self):
for view_name, widget in sorted(vn.items()):
yield (model_module, model_version, model_name, view_module, view_version, view_name), widget



# a registry of widgets by module, version, and name so we can create a Python model from widgets
# that are constructed from the frontend.
registry = WidgetRegistry()

def register(widget):
"""A decorator registering a widget class in the widget registry."""
w = widget.class_traits()
Widget._widget_types.register(w['_model_module'].default_value,
registry.register(w['_model_module'].default_value,
w['_model_module_version'].default_value,
w['_model_name'].default_value,
w['_view_module'].default_value,
Expand All @@ -272,22 +283,46 @@ def register(widget):
return widget


class _staticproperty(object):
def __init__(self, fget):
self.fget = fget

def __get__(self, owner_self, owner_cls):
assert owner_self is None
return self.fget()



class Widget(LoggingHasTraits):
#-------------------------------------------------------------------------
# Class attributes
#-------------------------------------------------------------------------
_widget_construction_callback = None
_control_comm = None

# _active_widgets is a dictionary of all active widget objects
_active_widgets = {}
@_staticproperty
def widgets():
warnings.warn("Widget.widgets is deprecated, use ipywidgets.widgets.widget.instances", DeprecationWarning)
return instances

@_staticproperty
def _active_widgets():
warnings.warn("Widget._active_widgets is deprecated, use ipywidgets.widgets.widget.instances", DeprecationWarning)
return instances

@_staticproperty
def _widget_types():
warnings.warn("Widget._widget_types is deprecated, use ipywidgets.widgets.widget.register", DeprecationWarning)
return registry

# _widget_types is a registry of widgets by module, version, and name:
_widget_types = WidgetRegistry()
@_staticproperty
def widget_types():
warnings.warn("Widget.widget_types is deprecated, use ipywidgets.widgets.widget.register", DeprecationWarning)
return registry

@classmethod
def close_all(cls):
for widget in list(cls._active_widgets.values()):
for widget in list(instances.values()):
widget.close()

@staticmethod
Expand Down Expand Up @@ -329,7 +364,7 @@ def _handle_control_comm_msg(cls, msg):
if method == 'request_states':
# Send back the full widgets state
cls.get_manager_state()
widgets = cls._active_widgets.values()
widgets = instances.values()
full_state = {}
drop_defaults = False
for widget in widgets:
Expand Down Expand Up @@ -359,7 +394,7 @@ def handle_comm_opened(comm, msg):
state = data['state']

# Find the widget class to instantiate in the registered widgets
widget_class = Widget._widget_types.get(state['_model_module'],
widget_class = register.get(state['_model_module'],
state['_model_module_version'],
state['_model_name'],
state['_view_module'],
Expand All @@ -380,7 +415,7 @@ def get_manager_state(drop_defaults=False, widgets=None):
"""
state = {}
if widgets is None:
widgets = Widget._active_widgets.values()
widgets = instances.values()
for widget in widgets:
state[widget.model_id] = widget._get_embed_state(drop_defaults=drop_defaults)
return {'version_major': 2, 'version_minor': 0, 'state': state}
Expand Down Expand Up @@ -476,7 +511,7 @@ def _comm_changed(self, change):
self._model_id = self.model_id

self.comm.on_msg(self._handle_msg)
Widget._active_widgets[self.model_id] = self
instances[self.model_id] = self

@property
def model_id(self):
Expand All @@ -496,7 +531,7 @@ def close(self):
When the comm is closed, all of the widget views are automatically
removed from the front-end."""
if self.comm is not None:
Widget._active_widgets.pop(self.model_id, None)
instances.pop(self.model_id, None)
self.comm.close()
self.comm = None
self._repr_mimebundle_ = None
Expand Down

0 comments on commit ca91f89

Please sign in to comment.