Skip to content

Commit ca39a1f

Browse files
committed
Misc changes + version 0.4.2 bump
Fixes an issue with typing for element functions - protocol from typing extensions does not produce correct behavior. Variable positional and keyword arguments should be optional and not required. Reworked html to vdom function output. Before it ignored the possibility of having multiple "root" elements - now, we wrap whatever models are produced in a div and the children are the roots of the html that was passed. Allows multiple transforms to be passed for html to vdom and changes how they are applied. Now they are run from roots to leafs instead of the other way around. Feels easier to thing about that way.
1 parent 90a2053 commit ca39a1f

File tree

8 files changed

+79
-80
lines changed

8 files changed

+79
-80
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<a href="https://travis-ci.com/rmorshea/idom">
44
<img alt="Build Status" src="https://travis-ci.com/rmorshea/idom.svg?branch=master"/>
55
</a>
6-
<a href="https://pypi.python.org/pypi/idom">
7-
<img alt="Version Info" src="https://img.shields.io/pypi/v/spectate.svg"/>
8-
</a>
96
<a href="https://github.com/rmorshea/idom/blob/master/LICENSE"/>
107
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg">
118
</a>
9+
<a href="https://pypi.python.org/pypi/idom">
10+
<img alt="Version Info" src="https://img.shields.io/pypi/v/spectate.svg"/>
11+
</a>
1212

1313
Libraries for creating and controlling interactive web pages with Python 3.6 and above.
1414

examples/introduction.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@
621621
"name": "python",
622622
"nbconvert_exporter": "python",
623623
"pygments_lexer": "ipython3",
624-
"version": "3.7.3"
624+
"version": "3.6.8"
625625
}
626626
},
627627
"nbformat": 4,

requirements/docs.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# copied from prod.txt
22
sanic >=18.12, <19.0
3-
typing-extensions >=3.7.2, <4.0
43

54
# copied from dev.txt
65
pytest

requirements/prod.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
sanic >=18.12, <19.0
2-
typing-extensions >=3.7.2, <4.0

src/py/idom/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.4.1"
1+
__version__ = "0.4.2"
22

33
from .core import element, Element, Events, Layout
44
from .widgets import node, Image, hotswap, display, html, Input

src/py/idom/core/element.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,15 @@
44
from functools import wraps
55
import time
66

7-
from typing_extensions import Protocol
8-
from typing import Dict, Callable, Any, List, Optional, overload, Awaitable, Mapping
7+
from typing import Dict, Callable, Any, List, Optional, overload, Awaitable
98

109
import idom
1110

1211
from .utils import bound_id
1312

1413

1514
ElementConstructor = Callable[..., "Element"] # Element constructor
16-
17-
18-
class _EF(Protocol):
19-
"""Element function."""
20-
21-
def __call__(
22-
self, element: "Element", *args: Any, **kwargs: Any
23-
) -> Awaitable[Dict[str, Any]]:
24-
...
15+
ElementRenderFunction = Callable[..., Awaitable[Any]]
2516

2617

2718
@overload
@@ -30,20 +21,22 @@ def element(function: Callable[..., Any]) -> ElementConstructor:
3021

3122

3223
@overload
33-
def element(*, state: Optional[str] = None) -> Callable[[_EF], ElementConstructor]:
24+
def element(
25+
*, state: Optional[str] = None
26+
) -> Callable[[ElementRenderFunction], ElementConstructor]:
3427
...
3528

3629

3730
def element(
38-
function: Optional[_EF] = None, state: Optional[str] = None
31+
function: Optional[ElementRenderFunction] = None, state: Optional[str] = None
3932
) -> Callable[..., Any]:
4033
"""A decorator for defining an :class:`Element`.
4134
4235
Parameters:
4336
function: The function that will render a :term:`VDOM` model.
4437
"""
4538

46-
def setup(func: _EF) -> ElementConstructor:
39+
def setup(func: ElementRenderFunction) -> ElementConstructor:
4740
@wraps(func)
4841
def constructor(*args: Any, **kwargs: Any) -> Element:
4942
element = Element(func, state)
@@ -75,7 +68,7 @@ def id(self) -> str:
7568
return self._element_id
7669

7770
@abc.abstractmethod
78-
async def render(self) -> Mapping[str, Any]:
71+
async def render(self) -> Any:
7972
...
8073

8174
def mount(self, layout: "idom.Layout") -> None:
@@ -134,7 +127,9 @@ class Element(AbstractElement):
134127
"_stop_animation",
135128
)
136129

137-
def __init__(self, function: _EF, state_parameters: Optional[str]):
130+
def __init__(
131+
self, function: ElementRenderFunction, state_parameters: Optional[str]
132+
):
138133
super().__init__()
139134
self._function = function
140135
self._function_signature = inspect.signature(function)
@@ -205,7 +200,7 @@ async def wrapper() -> None:
205200
else:
206201
return setup(function)
207202

208-
async def render(self) -> Mapping[str, Any]:
203+
async def render(self) -> Any:
209204
"""Render the element's :term:`VDOM` model."""
210205
# load update and reset for next render
211206
state = self._state

src/py/idom/tools.py

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from html.parser import HTMLParser as _HTMLParser
22

3-
from typing import List, Tuple, Any, Dict, Union, Optional, TypeVar, Generic, Callable
3+
from typing import List, Tuple, Any, Dict, TypeVar, Generic, Callable
44

55

66
_R = TypeVar("_R", bound=Any) # Var reference
@@ -41,18 +41,18 @@ def handle():
4141
return idom.node("button", "Use" eventHandlers=events)
4242
"""
4343

44-
__slots__ = ("__current",)
44+
__slots__ = ("_current",)
4545

4646
def __init__(self, value: _R) -> None:
47-
self.__current = value
47+
self._current = value
4848

4949
def set(self, new: _R) -> _R:
50-
old = self.__current
51-
self.__current = new
50+
old = self._current
51+
self._current = new
5252
return old
5353

5454
def get(self) -> _R:
55-
return self.__current
55+
return self._current
5656

5757
def __eq__(self, other: Any) -> bool:
5858
if isinstance(other, Var):
@@ -64,38 +64,46 @@ def __repr__(self) -> str:
6464
return "Var(%r)" % self.get()
6565

6666

67-
_ModelOrStr = Union[Dict[str, Any], str]
68-
_ModelTransform = Callable[[_ModelOrStr], None]
67+
_ModelTransform = Callable[[Dict[str, Any]], Any]
6968

7069

71-
def html_to_vdom(
72-
source: str, transform: Optional[_ModelTransform] = None
73-
) -> List[Dict[str, Any]]:
70+
def html_to_vdom(source: str, *transforms: _ModelTransform) -> Dict[str, Any]:
7471
"""Transform HTML into a DOM model
7572
7673
Parameters:
7774
source:
7875
The raw HTML as a string
79-
transform:
80-
A function for transforming each model as it is created. For example,
81-
you might use a transform function to add highlighting to a ``<code/>``
82-
block.
76+
transforms:
77+
Functions of the form ``transform(old) -> new`` where ``old`` is a VDOM
78+
dictionary which will be replaced by ``new``. You might use a transform
79+
function to add highlighting to a ``<code/>`` block.
8380
"""
84-
parser = HtmlParser(transform)
81+
parser = HtmlParser()
8582
parser.feed(source)
86-
return parser.model()
83+
root = parser.model()
84+
to_visit = [root]
85+
while to_visit:
86+
node = to_visit.pop(0)
87+
if isinstance(node, dict) and "children" in node:
88+
transformed = []
89+
for child in node["children"]:
90+
if isinstance(child, dict):
91+
for t in transforms:
92+
child = t(child)
93+
if child is not None:
94+
transformed.append(child)
95+
to_visit.append(child)
96+
node["children"] = transformed
97+
if "attributes" in node and not node["attributes"]:
98+
del node["attributes"]
99+
if "children" in node and not node["children"]:
100+
del node["children"]
101+
return root
87102

88103

89104
class HtmlParser(_HTMLParser):
90-
def __init__(self, transform: Optional[_ModelTransform] = None):
91-
super().__init__()
92-
if transform is not None:
93-
self._transform = transform
94-
95-
def model(self) -> List[Dict[str, Any]]:
96-
root: Dict[str, Any] = self._node_stack[0]
97-
root_children: List[Dict[str, Any]] = root["children"]
98-
return root_children
105+
def model(self) -> Dict[str, Any]:
106+
return self._node_stack[0]
99107

100108
def feed(self, data: str) -> None:
101109
self._node_stack.append(self._make_node("div", {}))
@@ -112,12 +120,7 @@ def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]) -> None:
112120
self._node_stack.append(new)
113121

114122
def handle_endtag(self, tag: str) -> None:
115-
node = self._node_stack.pop(-1)
116-
self._transform(node)
117-
if not node["attributes"]:
118-
del node["attributes"]
119-
if not node["children"]:
120-
del node["children"]
123+
del self._node_stack[-1]
121124

122125
def handle_data(self, data: str) -> None:
123126
self._node_stack[-1]["children"].append(data)
@@ -134,7 +137,3 @@ def _make_node(tag: str, attrs: Dict[str, Any]) -> Dict[str, Any]:
134137
style_dict[camel_case_key] = v
135138
attrs["style"] = style_dict
136139
return {"tagName": tag, "attributes": attrs, "children": []}
137-
138-
@staticmethod
139-
def _transform(node: Dict[str, Any]) -> None:
140-
...

src/py/tests/test_tools.py renamed to src/py/tests/test_idom/test_tools.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ def test_var_get():
5050
],
5151
)
5252
def test_html_to_vdom(case):
53-
assert html_to_vdom(case["source"]) == [case["model"]]
53+
assert html_to_vdom(case["source"]) == {
54+
"tagName": "div",
55+
"children": [case["model"]],
56+
}
5457

5558

5659
def test_html_to_vdom_transform():
@@ -59,23 +62,27 @@ def test_html_to_vdom_transform():
5962
def make_links_blue(node):
6063
if node["tagName"] == "a":
6164
node["attributes"]["style"] = {"color": "blue"}
65+
return node
6266

63-
assert html_to_vdom(source, make_links_blue) == [
64-
{
65-
"tagName": "p",
66-
"children": [
67-
"hello ",
68-
{
69-
"tagName": "a",
70-
"children": ["world"],
71-
"attributes": {"style": {"color": "blue"}},
72-
},
73-
" and ",
74-
{
75-
"tagName": "a",
76-
"children": ["universe"],
77-
"attributes": {"style": {"color": "blue"}},
78-
},
79-
],
80-
}
81-
]
67+
expected = {
68+
"tagName": "p",
69+
"children": [
70+
"hello ",
71+
{
72+
"tagName": "a",
73+
"children": ["world"],
74+
"attributes": {"style": {"color": "blue"}},
75+
},
76+
" and ",
77+
{
78+
"tagName": "a",
79+
"children": ["universe"],
80+
"attributes": {"style": {"color": "blue"}},
81+
},
82+
],
83+
}
84+
85+
assert html_to_vdom(source, make_links_blue) == {
86+
"tagName": "div",
87+
"children": [expected],
88+
}

0 commit comments

Comments
 (0)