Skip to content

Commit 4a3d960

Browse files
committed
misc updates
- minor doc changes - vdom function now access children as positional arguments the tradeoff here is that attributes must not contain a key named `tagName` (which isn't too much to ask). - updates of elements can now be triggered withing threads which means that you can call `self.update()` when `run_in_executor=True` was specified. - enable mypy --strict
1 parent 4115f9d commit 4a3d960

23 files changed

+394
-271
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ repos:
88
rev: 3.7.9
99
hooks:
1010
- id: flake8
11-
- repo: https://github.com/pre-commit/mirrors-mypy
12-
rev: v0.761
13-
hooks:
14-
- id: mypy
15-
additional_dependencies: [pyalect]
1611
- repo: https://github.com/kynan/nbstripout
1712
rev: master
1813
hooks:
1914
- id: nbstripout
2015
files: ".ipynb"
16+
- repo: https://github.com/pre-commit/mirrors-mypy
17+
rev: v0.770
18+
hooks:
19+
- id: mypy
20+
additional_dependencies: [pyalect, sanic<19.12.0]

docs/source/javascript-modules.rst

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,54 @@ Javascript Modules
33

44
.. note::
55

6-
This is recent feature of IDOM. If you have a problem following this tutorial
6+
This is a recent feature of IDOM. If you have a problem following this tutorial
77
`post an issue <https://github.com/rmorshea/idom/issues>`__.
88

9-
While IDOM is a great tool for displaying HTML and respond to browser events with
9+
While IDOM is a great tool for displaying HTML and responding to browser events with
1010
pure Python, there are other projects which already allow you to do this inside
1111
`Jupyter Notebooks <https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Basics.html>`__
1212
or in
1313
`webpages <https://blog.jupyter.org/and-voil%C3%A0-f6a2c08a4a93?gi=54b835a2fcce>`__.
1414
The real power of IDOM comes from its ability to seemlessly leverage the existing
1515
ecosystem of
1616
`React components <https://reactjs.org/docs/components-and-props.html>`__.
17+
1718
So long as your library of interest is an
1819
`ES Module <https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/>`__
1920
you could install using
2021
`Snowpack <https://www.snowpack.dev/>`__
21-
you can use it with IDOM (we're working to support non-standard packages too) [GH-166]_.
22+
you can use it with IDOM
2223
You can even define your own Javascript modules which use these third party Javascript
2324
packages.
2425

26+
.. note::
27+
28+
We're working to support non-standard packages too [GH166]_.
2529

2630
Installing React Components
2731
---------------------------
2832

29-
.. note::
33+
Before you start:
3034

31-
- Be sure that you've installed `npm <https://www.npmjs.com/get-npm>`__.
35+
- Be sure that you've installed `npm <https://www.npmjs.com/get-npm>`__.
3236

33-
- We're assuming the presence of a :ref:`Display Function` for our examples.
37+
- We're assuming the presence of a :ref:`Display Function` for our examples.
3438

3539
Once you've done this you can get started right away. In this example we'll be using a
3640
charting library for React called `Victory <https://formidable.com/open-source/victory/>`__.
3741
Installing it in IDOM is quite simple. Just create a :class:`~idom.widgets.utils.Module`,
38-
tell it what to install and specify ``install=True`` (we're working on a CLI for this) [GH-167]_:
42+
tell it what to install and specify ``install=True``.
3943

4044
.. code-block::
4145
4246
import idom
4347
# this may take a minute to download and install
4448
victory = idom.Module(name="victory", install=True)
4549
50+
.. note::
51+
52+
We're working on a CLI for this [GH167]_
53+
4654
You can install a specific version using ``install="[email protected]`` or any other
4755
standard javascript dependency specifier. Alternatively, if you need to access a module
4856
in a subfolder of your desired Javascript package, you can provide ``name="path/to/module"``

docs/source/known-issues.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Known Issues
22
============
33

4-
.. [GH-166] https://github.com/rmorshea/idom/issues/166
5-
.. [GH-167] https://github.com/rmorshea/idom/issues/167
4+
.. [GH166] https://github.com/rmorshea/idom/issues/166
5+
.. [GH167] https://github.com/rmorshea/idom/issues/167

examples/introduction.ipynb

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@
6969
"metadata": {},
7070
"outputs": [],
7171
"source": [
72-
"from idom.server import multiview_server\n",
72+
"from idom.server import multiview_server, find_available_port\n",
7373
"from idom.server.sanic import PerClientState\n",
7474
"from example_utils import display_href, example_server_url, pretty_dict_string\n",
7575
"\n",
76-
"host, port = \"127.0.0.1\", 8765\n",
76+
"host = \"127.0.0.1\"\n",
77+
"port = find_available_port(host)\n",
7778
"mount, server = multiview_server(PerClientState, host, port, {\"cors\": True}, {\"access_log\": False})\n",
7879
"server_url = example_server_url(host, port)\n",
7980
"\n",
@@ -229,7 +230,7 @@
229230
" task_input = idom.html.input({\"onKeyDown\": add_new_task})\n",
230231
" task_list = TaskList(items)\n",
231232
"\n",
232-
" return idom.html.div([task_input, task_list])\n",
233+
" return idom.html.div(task_input, task_list)\n",
233234
"\n",
234235
"\n",
235236
"@idom.element\n",
@@ -242,9 +243,9 @@
242243
" del items[index]\n",
243244
" self.update(items)\n",
244245
"\n",
245-
" task_text = idom.html.td([idom.html.p([text])])\n",
246-
" delete_button = idom.html.td({\"onClick\": remove}, [idom.html.button([\"x\"])])\n",
247-
" tasks.append(idom.html.tr([task_text, delete_button]))\n",
246+
" task_text = idom.html.td(idom.html.p(text))\n",
247+
" delete_button = idom.html.td({\"onClick\": remove}, idom.html.button(\"x\"))\n",
248+
" tasks.append(idom.html.tr(task_text, delete_button))\n",
248249
"\n",
249250
" return idom.html.table(tasks)\n",
250251
"\n",
@@ -327,14 +328,18 @@
327328
" diff = random.gauss(float(mu_var.get()), float(sigma_var.get()))\n",
328329
" y.append(y[-1] + diff)\n",
329330
" plot.update(x, y)\n",
330-
" \n",
331-
" style = idom.html.style([\"\"\"\n",
332-
" .linked-inputs {margin-bottom: 20px}\n",
333-
" .linked-inputs input {width: 48%;float: left}\n",
334-
" .linked-inputs input + input {margin-left: 4%}\n",
335-
" \"\"\"])\n",
336331
"\n",
337-
" return idom.html.div({\"style\": {\"width\": \"60%\"}}, [style, plot, mu_inputs, sigma_inputs])\n",
332+
" style = idom.html.style(\n",
333+
" \"\"\"\n",
334+
" .linked-inputs {margin-bottom: 20px}\n",
335+
" .linked-inputs input {width: 48%;float: left}\n",
336+
" .linked-inputs input + input {margin-left: 4%}\n",
337+
" \"\"\"\n",
338+
" )\n",
339+
"\n",
340+
" return idom.html.div(\n",
341+
" {\"style\": {\"width\": \"60%\"}}, style, plot, mu_inputs, sigma_inputs\n",
342+
" )\n",
338343
"\n",
339344
"\n",
340345
"@idom.element(run_in_executor=True)\n",
@@ -362,7 +367,7 @@
362367
"\n",
363368
" inputs.append(inp)\n",
364369
"\n",
365-
" fs = idom.html.fieldset({\"class\": \"linked-inputs\"}, [idom.html.legend(label)], inputs)\n",
370+
" fs = idom.html.fieldset({\"class\": \"linked-inputs\"}, idom.html.legend(label), inputs)\n",
366371
"\n",
367372
" return var, fs\n",
368373
"\n",
@@ -404,7 +409,7 @@
404409
"source": [
405410
"@idom.element\n",
406411
"async def DragDropBoxes(self):\n",
407-
" last_owner =idom.Var(None)\n",
412+
" last_owner = idom.Var(None)\n",
408413
" last_hover = idom.Var(None)\n",
409414
"\n",
410415
" h1 = Holder(\"filled\", last_owner, last_hover)\n",
@@ -413,44 +418,45 @@
413418
"\n",
414419
" last_owner.set(h1)\n",
415420
"\n",
416-
" style = idom.html.style([\"\"\"\n",
417-
" .holder {\n",
418-
" height: 150px;\n",
419-
" width: 150px;\n",
420-
" margin: 20px;\n",
421-
" display: inline-block;\n",
422-
" }\n",
423-
" .holder-filled {\n",
424-
" border: solid 10px black;\n",
425-
" background-color: black;\n",
426-
" }\n",
427-
" .holder-hover {\n",
428-
" border: dotted 5px black;\n",
429-
" }\n",
430-
" .holder-empty {\n",
431-
" border: solid 5px black;\n",
432-
" background-color: white;\n",
433-
" }\n",
434-
" \"\"\"])\n",
435-
"\n",
436-
" return idom.html.div([style, h1, h2, h3])\n",
421+
" style = idom.html.style(\n",
422+
" \"\"\"\n",
423+
" .holder {\n",
424+
" height: 150px;\n",
425+
" width: 150px;\n",
426+
" margin: 20px;\n",
427+
" display: inline-block;\n",
428+
" }\n",
429+
" .holder-filled {\n",
430+
" border: solid 10px black;\n",
431+
" background-color: black;\n",
432+
" }\n",
433+
" .holder-hover {\n",
434+
" border: dotted 5px black;\n",
435+
" }\n",
436+
" .holder-empty {\n",
437+
" border: solid 5px black;\n",
438+
" background-color: white;\n",
439+
" }\n",
440+
" \"\"\"\n",
441+
" )\n",
442+
"\n",
443+
" return idom.html.div(style, h1, h2, h3)\n",
437444
"\n",
438445
"\n",
439446
"@idom.element(state=\"last_owner, last_hover\")\n",
440447
"async def Holder(self, kind, last_owner, last_hover):\n",
441-
"\n",
442448
" @idom.event(prevent_default=True, stop_propagation=True)\n",
443449
" async def hover(event):\n",
444450
" if kind != \"hover\":\n",
445451
" self.update(\"hover\")\n",
446452
" old = last_hover.set(self)\n",
447453
" if old is not None and old is not self:\n",
448454
" old.update(\"empty\")\n",
449-
" \n",
455+
"\n",
450456
" async def start(event):\n",
451457
" last_hover.set(self)\n",
452458
" self.update(\"hover\")\n",
453-
" \n",
459+
"\n",
454460
" async def end(event):\n",
455461
" last_owner.get().update(\"filled\")\n",
456462
"\n",
@@ -463,15 +469,17 @@
463469
" old.update(\"empty\")\n",
464470
" self.update(\"filled\")\n",
465471
"\n",
466-
" return idom.html.div({\n",
467-
" \"draggable\": (kind == \"filled\"),\n",
468-
" \"onDragStart\": start,\n",
469-
" \"onDragOver\": hover,\n",
470-
" \"onDragEnd\": end,\n",
471-
" \"onDragLeave\": leave,\n",
472-
" \"onDrop\": dropped,\n",
473-
" \"class\": f\"holder-{kind} holder\",\n",
474-
" })\n",
472+
" return idom.html.div(\n",
473+
" {\n",
474+
" \"draggable\": (kind == \"filled\"),\n",
475+
" \"onDragStart\": start,\n",
476+
" \"onDragOver\": hover,\n",
477+
" \"onDragEnd\": end,\n",
478+
" \"onDragLeave\": leave,\n",
479+
" \"onDrop\": dropped,\n",
480+
" \"class\": f\"holder-{kind} holder\",\n",
481+
" }\n",
482+
" )\n",
475483
"\n",
476484
"\n",
477485
"print(\"Click and drag the black box onto the white one! 👆\")\n",
@@ -628,7 +636,7 @@
628636
" \n",
629637
" )\n",
630638
"\n",
631-
"\n",
639+
"first_view_mounted\n",
632640
"@idom.element(state=\"block_size\")\n",
633641
"async def Block(self, color, block_size):\n",
634642
" return idom.html.div(\n",
@@ -776,9 +784,11 @@
776784
"from idom.server.sanic import SharedClientState\n",
777785
"from example_utils import display_href, example_server_url, pretty_dict_string\n",
778786
"\n",
779-
"shared_server_url = example_server_url(\"127.0.0.1\", 5678)\n",
787+
"host = \"127.0.0.1\"\n",
788+
"port = find_available_port(host)\n",
789+
"shared_server_url = example_server_url(host, port)\n",
780790
"mount_shared, shared_server = hotswap_server(\n",
781-
" SharedClientState, \"127.0.0.1\", 5678, {\"cors\": True}, {\"access_log\": False}\n",
791+
" SharedClientState, host, port, {\"cors\": True}, {\"access_log\": False}\n",
782792
")\n",
783793
"\n",
784794
"def display_shared(element, *args, **kwargs):\n",

idom/client/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from loguru import logger
55
from pathlib import Path
66
from tempfile import TemporaryDirectory
7-
from typing import Optional, List, Union, Dict
7+
from typing import Optional, List, Union, Dict, Any
88

99

1010
CLIENT_DIR = Path(__file__).parent
@@ -73,7 +73,7 @@ def restore() -> None:
7373
_run_subprocess(["npm", "run", "snowpack"], CLIENT_DIR)
7474

7575

76-
def _package_json():
76+
def _package_json() -> Dict[str, Any]:
7777
with (CLIENT_DIR / "package.json").open("r") as f:
7878
dependencies = json.load(f)["dependencies"]
7979

@@ -91,7 +91,7 @@ def _package_json():
9191
}
9292

9393

94-
def _run_subprocess(args: List[str], cwd: Union[str, Path]):
94+
def _run_subprocess(args: List[str], cwd: Union[str, Path]) -> None:
9595
try:
9696
subprocess.run(
9797
args, cwd=cwd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
@@ -100,6 +100,7 @@ def _run_subprocess(args: List[str], cwd: Union[str, Path]):
100100
if error.stderr is not None:
101101
logger.error(error.stderr.decode())
102102
raise
103+
return None
103104

104105

105106
def _find_module_os_path(path: Path, name: str) -> Optional[Path]:

idom/core/element.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class AbstractElement(abc.ABC):
8686

8787
__slots__ = ["_layout"]
8888

89-
if not hasattr(abc.ABC, "__weakref__"):
89+
if not hasattr(abc.ABC, "__weakref__"): # pragma: no cover
9090
__slots__.append("__weakref__")
9191

9292
def __init__(self) -> None:
@@ -211,10 +211,8 @@ def setup(function: _ANM) -> _ANM:
211211

212212
async def animation() -> None:
213213
while True:
214-
await function(cancel_animation_future)
215-
# we need another await here in order to catch
216-
# a cancellation call (not sure why though)
217214
await pacer.wait()
215+
await function(cancel_animation_future)
218216

219217
# we store this future for later so we can cancel it
220218
future = asyncio.ensure_future(animation())
@@ -240,14 +238,13 @@ async def render(self) -> Any:
240238
# load update and reset for next render
241239
state = self._state
242240

243-
if state is None:
244-
raise RuntimeError(f"{self} cannot render - element has no state.")
245-
246241
for name in self._cross_update_parameters:
247242
if name not in state:
243+
# carry state across update calls implicitely
248244
if name in self._cross_update_state:
249245
state[name] = self._cross_update_state[name]
250246
else:
247+
# cross-update state parameter was set explicitely
251248
self._cross_update_state[name] = state[name]
252249

253250
self._state_updated = False
@@ -289,16 +286,16 @@ def _render_in_executor(self, state: Dict[str, Any]) -> Any:
289286
return result
290287

291288
def __repr__(self) -> str:
292-
qualname = getattr(self._function, "__qualname__", None)
293-
if qualname is not None:
294-
return "%s(%s)" % (qualname, self.id)
295-
else:
296-
return "%s(%r, %r)" % (type(self).__name__, self._function, self.id)
289+
total_state = {**self._cross_update_state, **self._state}
290+
state = ", ".join(f"{k}={v!r}" for k, v in total_state.items())
291+
return f"{self._function.__qualname__}({self.id}, {state})"
297292

298293

299294
class FramePacer:
300295
"""Simple utility for pacing frames in an animation loop."""
301296

297+
__slots__ = "_rate", "_last"
298+
302299
def __init__(self, rate: float):
303300
self._rate = rate
304301
self._last = time.time()

0 commit comments

Comments
 (0)