|
1 | 1 | """AnyWidget base class for custom Jupyter widgets.""" |
| 2 | + |
2 | 3 | from __future__ import annotations |
3 | 4 |
|
4 | | -from typing import Any |
| 5 | +from typing import Any, Callable |
5 | 6 |
|
6 | | -import ipywidgets |
7 | | -import traitlets.traitlets as t |
| 7 | +import traitlets |
8 | 8 |
|
9 | | -from ._file_contents import FileContents |
| 9 | +from ._descriptor import MimeBundleDescriptor |
10 | 10 | from ._util import ( |
11 | | - _ANYWIDGET_ID_KEY, |
12 | 11 | _CSS_KEY, |
13 | | - _DEFAULT_ESM, |
14 | 12 | _ESM_KEY, |
15 | | - enable_custom_widget_manager_once, |
16 | | - in_colab, |
17 | | - repr_mimebundle, |
18 | | - try_file_contents, |
19 | 13 | ) |
20 | | -from ._version import _ANYWIDGET_SEMVER_VERSION |
21 | 14 | from .experimental import _collect_anywidget_commands, _register_anywidget_commands |
22 | 15 |
|
23 | 16 |
|
24 | | -class AnyWidget(ipywidgets.DOMWidget): # type: ignore [misc] |
| 17 | +class AnyWidget(traitlets.HasTraits): # type: ignore [misc] |
25 | 18 | """Main AnyWidget base class.""" |
26 | 19 |
|
27 | | - _model_name = t.Unicode("AnyModel").tag(sync=True) |
28 | | - _model_module = t.Unicode("anywidget").tag(sync=True) |
29 | | - _model_module_version = t.Unicode(_ANYWIDGET_SEMVER_VERSION).tag(sync=True) |
30 | | - |
31 | | - _view_name = t.Unicode("AnyView").tag(sync=True) |
32 | | - _view_module = t.Unicode("anywidget").tag(sync=True) |
33 | | - _view_module_version = t.Unicode(_ANYWIDGET_SEMVER_VERSION).tag(sync=True) |
| 20 | + _repr_mimebundle_: MimeBundleDescriptor |
34 | 21 |
|
35 | 22 | def __init__(self, *args: Any, **kwargs: Any) -> None: |
36 | | - if in_colab(): |
37 | | - enable_custom_widget_manager_once() |
38 | | - |
39 | | - anywidget_traits = {} |
40 | | - for key in (_ESM_KEY, _CSS_KEY): |
41 | | - if hasattr(self, key) and not self.has_trait(key): |
42 | | - value = getattr(self, key) |
43 | | - anywidget_traits[key] = t.Unicode(str(value)).tag(sync=True) |
44 | | - if isinstance(value, FileContents): |
45 | | - value.changed.connect( |
46 | | - lambda new_contents, key=key: setattr(self, key, new_contents) |
47 | | - ) |
48 | | - |
49 | | - # show default _esm if not defined |
50 | | - if not hasattr(self, _ESM_KEY): |
51 | | - anywidget_traits[_ESM_KEY] = t.Unicode(_DEFAULT_ESM).tag(sync=True) |
52 | | - |
53 | | - # TODO: a better way to uniquely identify this subclasses? |
54 | | - # We use the fully-qualified name to get an id which we |
55 | | - # can use to update CSS if necessary. |
56 | | - anywidget_traits[_ANYWIDGET_ID_KEY] = t.Unicode( |
57 | | - f"{self.__class__.__module__}.{self.__class__.__name__}" |
58 | | - ).tag(sync=True) |
59 | | - |
60 | | - self.add_traits(**anywidget_traits) |
61 | 23 | super().__init__(*args, **kwargs) |
62 | 24 | _register_anywidget_commands(self) |
| 25 | + # Access _repr_mimebundle_ descriptor to trigger comm initialization |
| 26 | + self._repr_mimebundle_ # noqa: B018 |
63 | 27 |
|
64 | 28 | def __init_subclass__(cls, **kwargs: dict) -> None: |
65 | | - """Coerces _esm and _css to FileContents if they are files.""" |
| 29 | + """Create the _repr_mimebundle_ descriptor and register anywidget commands.""" |
66 | 30 | super().__init_subclass__(**kwargs) |
67 | | - for key in (_ESM_KEY, _CSS_KEY) & cls.__dict__.keys(): |
68 | | - # TODO: Upgrate to := when we drop Python 3.7 |
69 | | - file_contents = try_file_contents(getattr(cls, key)) |
70 | | - if file_contents: |
71 | | - setattr(cls, key, file_contents) |
| 31 | + extra_state = { |
| 32 | + key: getattr(cls, key) for key in (_ESM_KEY, _CSS_KEY) & cls.__dict__.keys() |
| 33 | + } |
| 34 | + cls._repr_mimebundle_ = MimeBundleDescriptor(**extra_state) |
72 | 35 | _collect_anywidget_commands(cls) |
73 | 36 |
|
74 | | - def _repr_mimebundle_(self, **kwargs: dict) -> tuple[dict, dict] | None: |
75 | | - plaintext = repr(self) |
76 | | - if len(plaintext) > 110: |
77 | | - plaintext = plaintext[:110] + "…" |
78 | | - if self._view_name is None: |
79 | | - return None # type: ignore[unreachable] |
80 | | - return repr_mimebundle(model_id=self.model_id, repr_text=plaintext) |
| 37 | + def send(self, msg: Any, buffers: list[memoryview] | None = None) -> None: |
| 38 | + """Send a message to the frontend.""" |
| 39 | + self._repr_mimebundle_.send(content=msg, buffers=buffers) |
| 40 | + |
| 41 | + def on_msg( |
| 42 | + self, callback: Callable[[Any, str | list | dict, list[bytes]], None] |
| 43 | + ) -> None: |
| 44 | + """Register a message handler.""" |
| 45 | + self._repr_mimebundle_.register_callback(callback) |
0 commit comments