Skip to content

Commit 4ea5b9a

Browse files
committed
move VdomDict and VdomJson to proto.py
also adds an `is_vdom` util function and makes VdomDictConstructor private
1 parent 8ccdf70 commit 4ea5b9a

File tree

10 files changed

+172
-107
lines changed

10 files changed

+172
-107
lines changed

docs/source/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ to allow users to enable this behavior early.
299299
- minor doc updates - :commit:`e5511d9`
300300
- add tests for callback identity preservation with keys - :commit:`72e03ec`
301301
- add 'key' to VDOM spec - :commit:`c3236fe`
302-
- Rename validate_serialized_vdom to validate_vdom - :commit:`d04faf9`
302+
- Rename validate_serialized_vdom to validate_vdom_json - :commit:`d04faf9`
303303
- EventHandler should not serialize itself - :commit:`f7a59f2`
304304
- fix docs typos - :commit:`42b2e20`
305305
- fixes: #331 - add roadmap to docs - :commit:`4226c12`

src/idom/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .core.component import Component, component
44
from .core.events import EventHandler, event
55
from .core.layout import Layout
6-
from .core.vdom import VdomDict, vdom
6+
from .core.vdom import vdom
77
from .server.prefab import run
88
from .utils import Ref, html_to_vdom
99
from .widgets import hotswap, multiview
@@ -28,6 +28,5 @@
2828
"Ref",
2929
"run",
3030
"vdom",
31-
"VdomDict",
3231
"web",
3332
]

src/idom/core/component.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from functools import wraps
1111
from typing import Any, Callable, Dict, Optional, Tuple, Union
1212

13-
from .proto import ComponentType
14-
from .vdom import VdomDict
13+
from .proto import ComponentType, VdomDict
1514

1615

1716
def component(

src/idom/core/dispatcher.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@
2626
from anyio import create_task_group
2727
from jsonpatch import apply_patch, make_patch
2828

29-
from idom.core.vdom import VdomJson
3029
from idom.utils import Ref
3130

3231
from .layout import LayoutEvent, LayoutUpdate
33-
from .proto import LayoutType
32+
from .proto import LayoutType, VdomJson
3433

3534

3635
logger = getLogger(__name__)

src/idom/core/layout.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from idom.utils import Ref
3131

3232
from .hooks import LifeCycleHook
33-
from .proto import ComponentType, EventHandlerDict
34-
from .vdom import VdomJson, validate_vdom
33+
from .proto import ComponentType, EventHandlerDict, VdomJson
34+
from .vdom import validate_vdom_json
3535

3636

3737
logger = getLogger(__name__)
@@ -154,7 +154,7 @@ async def render(self) -> LayoutUpdate:
154154
# Ensure that the model is valid VDOM on each render
155155
root_id = self._root_life_cycle_state_id
156156
root_model = self._model_states_by_life_cycle_state_id[root_id]
157-
validate_vdom(root_model.model.current)
157+
validate_vdom_json(root_model.model.current)
158158
return result
159159

160160
def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdate:

src/idom/core/proto.py

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@
77

88
from types import TracebackType
99
from typing import (
10-
TYPE_CHECKING,
1110
Any,
1211
Callable,
1312
Dict,
13+
Iterable,
1414
List,
1515
Mapping,
1616
Optional,
17+
Sequence,
1718
Type,
1819
TypeVar,
20+
Union,
1921
)
2022

21-
from typing_extensions import Protocol, runtime_checkable
22-
23-
24-
if TYPE_CHECKING: # pragma: no cover
25-
from .vdom import VdomDict
23+
from typing_extensions import Protocol, TypedDict, runtime_checkable
2624

2725

2826
ComponentConstructor = Callable[..., "ComponentType"]
@@ -64,6 +62,77 @@ def __exit__(
6462
"""Clean up the view after its final render"""
6563

6664

65+
VdomAttributes = Mapping[str, Any]
66+
"""Describes the attributes of a :class:`VdomDict`"""
67+
68+
VdomChild = Union[ComponentType, "VdomDict", str]
69+
"""A single child element of a :class:`VdomDict`"""
70+
71+
VdomChildren = Sequence[VdomChild]
72+
"""Describes a series of :class:`VdomChild` elements"""
73+
74+
VdomAttributesAndChildren = Union[
75+
Mapping[str, Any], # this describes both VdomDict and VdomAttributes
76+
Iterable[VdomChild],
77+
]
78+
"""Useful for the ``*attributes_and_children`` parameter in :func:`idom.core.vdom.vdom`"""
79+
80+
81+
class _VdomDictOptional(TypedDict, total=False):
82+
key: str
83+
children: Sequence[
84+
# recursive types are not allowed yet:
85+
# https://github.com/python/mypy/issues/731
86+
Union[ComponentType, Dict[str, Any], str]
87+
]
88+
attributes: VdomAttributes
89+
eventHandlers: EventHandlerDict # noqa
90+
importSource: ImportSourceDict # noqa
91+
92+
93+
class _VdomDictRequired(TypedDict, total=True):
94+
tagName: str # noqa
95+
96+
97+
class VdomDict(_VdomDictRequired, _VdomDictOptional):
98+
"""A :ref:`VDOM` dictionary"""
99+
100+
101+
class ImportSourceDict(TypedDict):
102+
source: str
103+
fallback: Any
104+
sourceType: str # noqa
105+
unmountBeforeUpdate: bool # noqa
106+
107+
108+
class _OptionalVdomJson(TypedDict, total=False):
109+
key: str
110+
error: str
111+
children: List[Any]
112+
attributes: Dict[str, Any]
113+
eventHandlers: Dict[str, _JsonEventTarget] # noqa
114+
importSource: _JsonImportSource # noqa
115+
116+
117+
class _RequiredVdomJson(TypedDict, total=True):
118+
tagName: str # noqa
119+
120+
121+
class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
122+
"""A JSON serializable form of :class:`VdomDict` matching the :data:`VDOM_JSON_SCHEMA`"""
123+
124+
125+
class _JsonEventTarget(TypedDict):
126+
target: str
127+
preventDefault: bool # noqa
128+
stopPropagation: bool # noqa
129+
130+
131+
class _JsonImportSource(TypedDict):
132+
source: str
133+
fallback: Any
134+
135+
67136
EventHandlerMapping = Mapping[str, "EventHandlerType"]
68137
"""A generic mapping between event names to their handlers"""
69138

src/idom/core/vdom.py

Lines changed: 46 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,9 @@
77

88
import inspect
99
import logging
10-
from typing import (
11-
Any,
12-
Dict,
13-
Iterable,
14-
List,
15-
Mapping,
16-
Optional,
17-
Sequence,
18-
Tuple,
19-
Union,
20-
cast,
21-
)
10+
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, cast
2211

2312
from fastjsonschema import compile as compile_json_schema
24-
from mypy_extensions import TypedDict
2513
from typing_extensions import Protocol
2614

2715
from idom.config import IDOM_DEBUG_MODE
@@ -30,7 +18,15 @@
3018
merge_event_handlers,
3119
to_event_handler_function,
3220
)
33-
from idom.core.proto import EventHandlerDict, EventHandlerMapping, EventHandlerType
21+
from idom.core.proto import (
22+
EventHandlerDict,
23+
EventHandlerMapping,
24+
EventHandlerType,
25+
ImportSourceDict,
26+
VdomAttributesAndChildren,
27+
VdomDict,
28+
VdomJson,
29+
)
3430

3531

3632
logger = logging.getLogger()
@@ -106,23 +102,41 @@
106102
_COMPILED_VDOM_VALIDATOR = compile_json_schema(VDOM_JSON_SCHEMA)
107103

108104

109-
def validate_vdom(value: Any) -> VdomJson:
105+
def validate_vdom_json(value: Any) -> VdomJson:
110106
"""Validate serialized VDOM - see :attr:`VDOM_JSON_SCHEMA` for more info"""
111107
_COMPILED_VDOM_VALIDATOR(value)
112108
return cast(VdomJson, value)
113109

114110

115-
_AttributesAndChildrenArg = Union[Mapping[str, Any], str, Iterable[Any], Any]
116-
_EventHandlersArg = Optional[EventHandlerMapping]
117-
_ImportSourceArg = Optional["ImportSourceDict"]
111+
def is_vdom(value: Any) -> bool:
112+
"""Return whether a value is a :class:`VdomDict`
113+
114+
This employs a very simple heuristic - something is VDOM if:
115+
116+
1. It is a ``dict`` instance
117+
2. It contains the key ``"tagName"``
118+
3. The value of the key ``"tagName"`` is a string
119+
120+
.. note::
121+
122+
Performing an ``isinstance(value, VdomDict)`` check is too restrictive since the
123+
user would be forced to import ``VdomDict`` every time they needed to declare a
124+
VDOM element. Giving the user more flexibility, at the cost of this check's
125+
accuracy, is worth it.
126+
"""
127+
return (
128+
isinstance(value, dict)
129+
and "tagName" in value
130+
and isinstance(value["tagName"], str)
131+
)
118132

119133

120134
def vdom(
121135
tag: str,
122-
*attributes_and_children: _AttributesAndChildrenArg,
136+
*attributes_and_children: VdomAttributesAndChildren,
123137
key: str = "",
124-
event_handlers: _EventHandlersArg = None,
125-
import_source: _ImportSourceArg = None,
138+
event_handlers: Optional[EventHandlerMapping] = None,
139+
import_source: Optional[ImportSourceDict] = None,
126140
) -> VdomDict:
127141
"""A helper function for creating VDOM dictionaries.
128142
@@ -169,28 +183,31 @@ def vdom(
169183
return model
170184

171185

172-
class VdomDictConstructor(Protocol):
186+
class _VdomDictConstructor(Protocol):
173187
def __call__(
174188
self,
175-
*args: _AttributesAndChildrenArg,
176-
event_handlers: _EventHandlersArg = None,
177-
import_source: _ImportSourceArg = None,
189+
*attributes_and_children: VdomAttributesAndChildren,
190+
key: str = ...,
191+
event_handlers: Optional[EventHandlerMapping] = ...,
192+
import_source: Optional[ImportSourceDict] = ...,
178193
) -> VdomDict:
179194
...
180195

181196

182-
def make_vdom_constructor(tag: str, allow_children: bool = True) -> VdomDictConstructor:
197+
def make_vdom_constructor(
198+
tag: str, allow_children: bool = True
199+
) -> _VdomDictConstructor:
183200
"""Return a constructor for VDOM dictionaries with the given tag name.
184201
185202
The resulting callable will have the same interface as :func:`vdom` but without its
186203
first ``tag`` argument.
187204
"""
188205

189206
def constructor(
190-
*attributes_and_children: _AttributesAndChildrenArg,
207+
*attributes_and_children: VdomAttributesAndChildren,
191208
key: str = "",
192-
event_handlers: _EventHandlersArg = None,
193-
import_source: _ImportSourceArg = None,
209+
event_handlers: Optional[EventHandlerMapping] = None,
210+
import_source: Optional[ImportSourceDict] = None,
194211
) -> VdomDict:
195212
model = vdom(
196213
tag,
@@ -319,54 +336,3 @@ def _is_single_child(value: Any) -> bool:
319336
logger.error(f"Key not specified for dynamic child {child}")
320337

321338
return False
322-
323-
324-
class _VdomDictOptional(TypedDict, total=False):
325-
key: str
326-
children: Sequence[Any]
327-
attributes: Dict[str, Any]
328-
eventHandlers: EventHandlerDict # noqa
329-
importSource: ImportSourceDict # noqa
330-
331-
332-
class _VdomDictRequired(TypedDict, total=True):
333-
tagName: str # noqa
334-
335-
336-
class VdomDict(_VdomDictRequired, _VdomDictOptional):
337-
"""A :ref:`VDOM` dictionary"""
338-
339-
340-
class ImportSourceDict(TypedDict):
341-
source: str
342-
fallback: Any
343-
sourceType: str # noqa
344-
unmountBeforeUpdate: bool # noqa
345-
346-
347-
class _OptionalVdomJson(TypedDict, total=False):
348-
key: str
349-
error: str
350-
children: List[Any]
351-
attributes: Dict[str, Any]
352-
eventHandlers: Dict[str, _JsonEventTarget] # noqa
353-
importSource: _JsonImportSource # noqa
354-
355-
356-
class _RequiredVdomJson(TypedDict, total=True):
357-
tagName: str # noqa
358-
359-
360-
class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
361-
"""A JSON serializable form of :class:`VdomDict` matching the :data:`VDOM_JSON_SCHEMA`"""
362-
363-
364-
class _JsonEventTarget(TypedDict):
365-
target: str
366-
preventDefault: bool # noqa
367-
stopPropagation: bool # noqa
368-
369-
370-
class _JsonImportSource(TypedDict):
371-
source: str
372-
fallback: Any

0 commit comments

Comments
 (0)