Skip to content

Commit a3b2f4a

Browse files
committed
use non-editable install
1 parent ea485a4 commit a3b2f4a

File tree

6 files changed

+86
-135
lines changed

6 files changed

+86
-135
lines changed

docs/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ COPY setup.py ./
2626
COPY pyproject.toml ./
2727
COPY MANIFEST.in ./
2828
COPY README.md ./
29-
RUN pip install -e .[all]
29+
RUN pip install .[all]
3030

3131
# COPY License
3232
# -----------

scripts/one_example.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import idom
77
from docs.examples import all_example_names, get_example_files_by_name, load_one_example
8-
from idom.widgets import hotswap
8+
from idom.widgets import _hotswap
99

1010

1111
EXAMPLE_NAME_SET = all_example_names()
@@ -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(update_on_change=True)
3636

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

src/idom/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .core.serve import Stop
2020
from .core.vdom import vdom
2121
from .utils import Ref, html_to_vdom, vdom_to_html
22-
from .widgets import hotswap
22+
from .widgets import _hotswap
2323

2424

2525
__author__ = "idom-team"
@@ -32,7 +32,7 @@
3232
"create_context",
3333
"event",
3434
"hooks",
35-
"hotswap",
35+
"_hotswap",
3636
"html_to_vdom",
3737
"html",
3838
"Layout",

src/idom/testing/backend.py

+72-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import logging
55
from contextlib import AsyncExitStack
66
from types import TracebackType
7-
from typing import Any, Optional, Tuple, Type, Union
7+
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
1011
from idom.backend import default as default_server
1112
from idom.backend.types import BackendImplementation
1213
from idom.backend.utils import find_available_port
13-
from idom.widgets import hotswap
14+
from idom.core.hooks import use_callback
15+
from idom.core.types import ComponentConstructor
16+
from idom.utils import Ref
1417

1518
from .logs import LogAssertionError, capture_idom_logs, list_logged_exceptions
1619

@@ -41,7 +44,7 @@ def __init__(
4144
) -> None:
4245
self.host = host
4346
self.port = port or find_available_port(host, allow_reuse_waiting_ports=False)
44-
self.mount, self._root_component = hotswap()
47+
self.mount, self._root_component = _hotswap()
4548

4649
if app is not None:
4750
if implementation is None:
@@ -146,3 +149,69 @@ async def __aexit__(
146149
raise LogAssertionError("Unexpected logged exception") from logged_errors[0]
147150

148151
return None
152+
153+
154+
_MountFunc = Callable[["Callable[[], Any] | None"], None]
155+
156+
157+
def _hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]:
158+
"""Swap out components from a layout on the fly.
159+
160+
Since you can't change the component functions used to create a layout
161+
in an imperative manner, you can use ``hotswap`` to do this so
162+
long as you set things up ahead of time.
163+
164+
Parameters:
165+
update_on_change: Whether or not all views of the layout should be udpated on a swap.
166+
167+
Example:
168+
.. code-block:: python
169+
170+
import idom
171+
172+
show, root = idom.hotswap()
173+
PerClientStateServer(root).run_in_thread("localhost", 8765)
174+
175+
@idom.component
176+
def DivOne(self):
177+
return {"tagName": "div", "children": [1]}
178+
179+
show(DivOne)
180+
181+
# displaying the output now will show DivOne
182+
183+
@idom.component
184+
def DivTwo(self):
185+
return {"tagName": "div", "children": [2]}
186+
187+
show(DivTwo)
188+
189+
# displaying the output now will show DivTwo
190+
"""
191+
constructor_ref: Ref[Callable[[], Any]] = Ref(lambda: None)
192+
193+
set_constructor_callbacks: set[Callable[[Callable[[], Any]], None]] = set()
194+
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))
200+
201+
def add_callback() -> Callable[[], None]:
202+
set_constructor_callbacks.add(set_constructor)
203+
return lambda: set_constructor_callbacks.remove(set_constructor)
204+
205+
use_effect(add_callback)
206+
207+
return constructor()
208+
209+
def swap(constructor: Callable[[], Any] | None) -> None:
210+
constructor = constructor_ref.current = constructor or (lambda: None)
211+
212+
for set_constructor in set_constructor_callbacks:
213+
set_constructor(constructor)
214+
215+
return None
216+
217+
return swap, HotSwap

src/idom/widgets.py

+9-82
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
TypeVar,
1414
Union,
1515
)
16+
from warnings import warn
1617

1718
from typing_extensions import Protocol
1819

@@ -22,6 +23,7 @@
2223
from .core import hooks
2324
from .core.component import component
2425
from .core.types import ComponentConstructor, VdomDict
26+
from .testing.backend import _hotswap, _MountFunc
2527
from .utils import Ref
2628

2729

@@ -107,85 +109,10 @@ def __call__(self, value: str) -> _CastTo:
107109
...
108110

109111

110-
MountFunc = Callable[["Callable[[], Any] | None"], None]
111-
112-
113-
def hotswap(update_on_change: bool = False) -> Tuple[MountFunc, ComponentConstructor]:
114-
"""Swap out components from a layout on the fly.
115-
116-
Since you can't change the component functions used to create a layout
117-
in an imperative manner, you can use ``hotswap`` to do this so
118-
long as you set things up ahead of time.
119-
120-
Parameters:
121-
update_on_change: Whether or not all views of the layout should be udpated on a swap.
122-
123-
Example:
124-
.. code-block:: python
125-
126-
import idom
127-
128-
show, root = idom.hotswap()
129-
PerClientStateServer(root).run_in_thread("localhost", 8765)
130-
131-
@idom.component
132-
def DivOne(self):
133-
return {"tagName": "div", "children": [1]}
134-
135-
show(DivOne)
136-
137-
# displaying the output now will show DivOne
138-
139-
@idom.component
140-
def DivTwo(self):
141-
return {"tagName": "div", "children": [2]}
142-
143-
show(DivTwo)
144-
145-
# displaying the output now will show DivTwo
146-
"""
147-
constructor_ref: Ref[Callable[[], Any]] = Ref(lambda: None)
148-
149-
if update_on_change:
150-
set_constructor_callbacks: Set[Callable[[Callable[[], Any]], None]] = set()
151-
152-
@component
153-
def HotSwap() -> Any:
154-
# new displays will adopt the latest constructor and arguments
155-
constructor, set_constructor = _use_callable(constructor_ref.current)
156-
157-
def add_callback() -> Callable[[], None]:
158-
set_constructor_callbacks.add(set_constructor)
159-
return lambda: set_constructor_callbacks.remove(set_constructor)
160-
161-
hooks.use_effect(add_callback)
162-
163-
return constructor()
164-
165-
def swap(constructor: Callable[[], Any] | None) -> None:
166-
constructor = constructor_ref.current = constructor or (lambda: None)
167-
168-
for set_constructor in set_constructor_callbacks:
169-
set_constructor(constructor)
170-
171-
return None
172-
173-
else:
174-
175-
@component
176-
def HotSwap() -> Any:
177-
return constructor_ref.current()
178-
179-
def swap(constructor: Callable[[], Any] | None) -> None:
180-
constructor_ref.current = constructor or (lambda: None)
181-
return None
182-
183-
return swap, HotSwap
184-
185-
186-
_Func = Callable[..., Any]
187-
188-
189-
def _use_callable(initial_func: _Func) -> Tuple[_Func, Callable[[_Func], None]]:
190-
state, set_state = hooks.use_state(lambda: initial_func)
191-
return state, lambda new: set_state(lambda old: new)
112+
def hotswap(update_on_change: bool = False) -> Tuple[_MountFunc, ComponentConstructor]:
113+
warn(
114+
"The 'hotswap' function is deprecated and will be removed in a future release",
115+
DeprecationWarning,
116+
stacklevel=2,
117+
)
118+
return _hotswap(update_on_change)

tests/test_widgets.py

-45
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,6 @@
99
HERE = Path(__file__).parent
1010

1111

12-
async def test_hostwap_update_on_change(display: DisplayFixture):
13-
"""Ensure shared hotswapping works
14-
15-
This basically means that previously rendered views of a hotswap component get updated
16-
when a new view is mounted, not just the next time it is re-displayed
17-
18-
In this test we construct a scenario where clicking a button will cause a pre-existing
19-
hotswap component to be updated
20-
"""
21-
22-
def make_next_count_constructor(count):
23-
"""We need to construct a new function so they're different when we set_state"""
24-
25-
def constructor():
26-
count.current += 1
27-
return idom.html.div({"id": f"hotswap-{count.current}"}, count.current)
28-
29-
return constructor
30-
31-
@idom.component
32-
def ButtonSwapsDivs():
33-
count = idom.Ref(0)
34-
35-
async def on_click(event):
36-
mount(make_next_count_constructor(count))
37-
38-
incr = idom.html.button({"onClick": on_click, "id": "incr-button"}, "incr")
39-
40-
mount, make_hostswap = idom.widgets.hotswap(update_on_change=True)
41-
mount(make_next_count_constructor(count))
42-
hotswap_view = make_hostswap()
43-
44-
return idom.html.div(incr, hotswap_view)
45-
46-
await display.show(ButtonSwapsDivs)
47-
48-
client_incr_button = await display.page.wait_for_selector("#incr-button")
49-
50-
await display.page.wait_for_selector("#hotswap-1")
51-
await client_incr_button.click()
52-
await display.page.wait_for_selector("#hotswap-2")
53-
await client_incr_button.click()
54-
await display.page.wait_for_selector("#hotswap-3")
55-
56-
5712
IMAGE_SRC_BYTES = b"""
5813
<svg width="400" height="110" xmlns="http://www.w3.org/2000/svg">
5914
<rect width="300" height="100" style="fill:rgb(0,0,255);" />

0 commit comments

Comments
 (0)