Skip to content

Commit 0b208c6

Browse files
committed
deprecate hotswap
1 parent 23c0066 commit 0b208c6

File tree

7 files changed

+100
-52
lines changed

7 files changed

+100
-52
lines changed

docs/source/about/changelog.rst

+5-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ more info, see the :ref:`Contributor Guide <Creating a Changelog Entry>`.
2323
Unreleased
2424
----------
2525

26-
No changes.
26+
**Deprecated**
27+
28+
- :pull:`876` - ``idom.widgets.hotswap``. The function has no clear uses outside of some
29+
internal applications. For this reason it has been deprecated.
2730

2831

2932
v0.43.0
3033
-------
3134
:octicon:`milestone` *released on 2023-01-09*
3235

33-
**Removed**
36+
**Deprecated**
3437

3538
- :pull:`870` - ``ComponentType.should_render()``. This method was implemented based on
3639
reading the React/Preact source code. As it turns out though it seems like it's mostly

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ max-complexity = 18
5252
select = ["B", "C", "E", "F", "W", "T4", "B9", "N", "ROH"]
5353
exclude = ["**/node_modules/*", ".eggs/*", ".tox/*"]
5454
# -- flake8-tidy-imports --
55-
ban-relative-imports = "parents"
55+
ban-relative-imports = "true"

scripts/one_example.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def watch_for_change():
3232
def main():
3333
ex_name = _example_name_input()
3434

35-
mount, component = _hotswap(update_on_change=True)
35+
mount, component = _hotswap()
3636

3737
def update_component():
3838
print(f"Loading example: {ex_name!r}")

src/idom/__init__.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from . import backend, config, html, logging, sample, svg, types, web
2-
from .backend.hooks import use_connection, use_location, use_scope
3-
from .backend.utils import run
4-
from .core import hooks
5-
from .core.component import component
6-
from .core.events import event
7-
from .core.hooks import (
1+
from idom import backend, config, html, logging, sample, svg, types, web, widgets
2+
from idom.backend.hooks import use_connection, use_location, use_scope
3+
from idom.backend.utils import run
4+
from idom.core import hooks
5+
from idom.core.component import component
6+
from idom.core.events import event
7+
from idom.core.hooks import (
88
create_context,
99
use_callback,
1010
use_context,
@@ -15,11 +15,10 @@
1515
use_ref,
1616
use_state,
1717
)
18-
from .core.layout import Layout
19-
from .core.serve import Stop
20-
from .core.vdom import vdom
21-
from .utils import Ref, html_to_vdom, vdom_to_html
22-
from .widgets import _hotswap
18+
from idom.core.layout import Layout
19+
from idom.core.serve import Stop
20+
from idom.core.vdom import vdom
21+
from idom.utils import Ref, html_to_vdom, vdom_to_html
2322

2423

2524
__author__ = "idom-team"
@@ -32,7 +31,6 @@
3231
"create_context",
3332
"event",
3433
"hooks",
35-
"_hotswap",
3634
"html_to_vdom",
3735
"html",
3836
"Layout",
@@ -57,4 +55,5 @@
5755
"vdom_to_html",
5856
"vdom",
5957
"web",
58+
"widgets",
6059
]

src/idom/testing/backend.py

+29-18
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
from typing import Any, Callable, Optional, Tuple, Type, Union
88
from urllib.parse import urlencode, urlunparse
99

10-
from idom import component, use_effect, use_state
1110
from idom.backend import default as default_server
1211
from idom.backend.types import BackendImplementation
1312
from idom.backend.utils import find_available_port
14-
from idom.core.hooks import use_callback
13+
from idom.core.component import component
14+
from idom.core.hooks import use_callback, use_effect, use_state
1515
from idom.core.types import ComponentConstructor
1616
from idom.utils import Ref
1717

@@ -190,28 +190,39 @@ def DivTwo(self):
190190
"""
191191
constructor_ref: Ref[Callable[[], Any]] = Ref(lambda: None)
192192

193-
set_constructor_callbacks: set[Callable[[Callable[[], Any]], None]] = set()
193+
if update_on_change:
194+
set_constructor_callbacks: set[Callable[[Callable[[], Any]], None]] = set()
194195

195-
@component
196-
def HotSwap() -> Any:
197-
# new displays will adopt the latest constructor and arguments
198-
constructor, _set_constructor = use_state(lambda: constructor_ref.current)
199-
set_constructor = use_callback(lambda new: _set_constructor(lambda _: new))
196+
@component
197+
def HotSwap() -> Any:
198+
# new displays will adopt the latest constructor and arguments
199+
constructor, _set_constructor = use_state(lambda: constructor_ref.current)
200+
set_constructor = use_callback(lambda new: _set_constructor(lambda _: new))
200201

201-
def add_callback() -> Callable[[], None]:
202-
set_constructor_callbacks.add(set_constructor)
203-
return lambda: set_constructor_callbacks.remove(set_constructor)
202+
def add_callback() -> Callable[[], None]:
203+
set_constructor_callbacks.add(set_constructor)
204+
return lambda: set_constructor_callbacks.remove(set_constructor)
204205

205-
use_effect(add_callback)
206+
use_effect(add_callback)
206207

207-
return constructor()
208+
return constructor()
208209

209-
def swap(constructor: Callable[[], Any] | None) -> None:
210-
constructor = constructor_ref.current = constructor or (lambda: None)
210+
def swap(constructor: Callable[[], Any] | None) -> None:
211+
constructor = constructor_ref.current = constructor or (lambda: None)
211212

212-
for set_constructor in set_constructor_callbacks:
213-
set_constructor(constructor)
213+
for set_constructor in set_constructor_callbacks:
214+
set_constructor(constructor)
214215

215-
return None
216+
return None
217+
218+
else:
219+
220+
@component
221+
def HotSwap() -> Any:
222+
return constructor_ref.current()
223+
224+
def swap(constructor: Callable[[], Any] | None) -> None:
225+
constructor_ref.current = constructor or (lambda: None)
226+
return None
216227

217228
return swap, HotSwap

src/idom/widgets.py

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,16 @@
11
from __future__ import annotations
22

33
from base64 import b64encode
4-
from typing import (
5-
Any,
6-
Callable,
7-
Dict,
8-
List,
9-
Optional,
10-
Sequence,
11-
Set,
12-
Tuple,
13-
TypeVar,
14-
Union,
15-
)
4+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, TypeVar, Union
165
from warnings import warn
176

187
from typing_extensions import Protocol
198

209
import idom
2110

2211
from . import html
23-
from .core import hooks
24-
from .core.component import component
2512
from .core.types import ComponentConstructor, VdomDict
2613
from .testing.backend import _hotswap, _MountFunc
27-
from .utils import Ref
2814

2915

3016
def image(
@@ -109,7 +95,9 @@ def __call__(self, value: str) -> _CastTo:
10995
...
11096

11197

112-
def hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]:
98+
def hotswap(
99+
update_on_change: bool = False,
100+
) -> Tuple[_MountFunc, ComponentConstructor]: # pragma: no cover
113101
warn(
114102
"The 'hotswap' function is deprecated and will be removed in a future release",
115103
DeprecationWarning,

tests/test_testing.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
import pytest
55

6-
from idom import testing
6+
from idom import Ref, component, html, testing
77
from idom.backend import starlette as starlette_implementation
88
from idom.logging import ROOT_LOGGER
99
from idom.sample import SampleApp as SampleApp
10+
from idom.testing.backend import _hotswap
11+
from idom.testing.display import DisplayFixture
1012

1113

1214
def test_assert_idom_logged_does_not_supress_errors():
@@ -162,3 +164,48 @@ def test_list_logged_excptions():
162164

163165
logged_errors = testing.logs.list_logged_exceptions(records)
164166
assert logged_errors == [the_error]
167+
168+
169+
async def test_hostwap_update_on_change(display: DisplayFixture):
170+
"""Ensure shared hotswapping works
171+
172+
This basically means that previously rendered views of a hotswap component get updated
173+
when a new view is mounted, not just the next time it is re-displayed
174+
175+
In this test we construct a scenario where clicking a button will cause a pre-existing
176+
hotswap component to be updated
177+
"""
178+
179+
def make_next_count_constructor(count):
180+
"""We need to construct a new function so they're different when we set_state"""
181+
182+
def constructor():
183+
count.current += 1
184+
return html.div({"id": f"hotswap-{count.current}"}, count.current)
185+
186+
return constructor
187+
188+
@component
189+
def ButtonSwapsDivs():
190+
count = Ref(0)
191+
192+
async def on_click(event):
193+
mount(make_next_count_constructor(count))
194+
195+
incr = html.button({"onClick": on_click, "id": "incr-button"}, "incr")
196+
197+
mount, make_hostswap = _hotswap(update_on_change=True)
198+
mount(make_next_count_constructor(count))
199+
hotswap_view = make_hostswap()
200+
201+
return html.div(incr, hotswap_view)
202+
203+
await display.show(ButtonSwapsDivs)
204+
205+
client_incr_button = await display.page.wait_for_selector("#incr-button")
206+
207+
await display.page.wait_for_selector("#hotswap-1")
208+
await client_incr_button.click()
209+
await display.page.wait_for_selector("#hotswap-2")
210+
await client_incr_button.click()
211+
await display.page.wait_for_selector("#hotswap-3")

0 commit comments

Comments
 (0)