|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 | 3 | import inspect
|
| 4 | +from collections.abc import Sequence |
4 | 5 | from functools import wraps
|
5 |
| -from typing import Any, Callable, Dict, Optional, Tuple |
| 6 | +from typing import Any, Callable |
6 | 7 |
|
7 |
| -from .types import ComponentType, VdomDict |
| 8 | +from .types import Key, RenderResult |
| 9 | +from .vdom import flatten_children |
8 | 10 |
|
9 | 11 |
|
10 |
| -def component( |
11 |
| - function: Callable[..., ComponentType | VdomDict | str | None] |
12 |
| -) -> Callable[..., Component]: |
| 12 | +def component(function: Callable[..., RenderResult]) -> Callable[..., Component]: |
13 | 13 | """A decorator for defining a new component.
|
14 | 14 |
|
15 | 15 | Parameters:
|
16 | 16 | function: The component's :meth:`idom.core.proto.ComponentType.render` function.
|
17 | 17 | """
|
18 | 18 | sig = inspect.signature(function)
|
19 | 19 |
|
20 |
| - if "key" in sig.parameters and sig.parameters["key"].kind in ( |
21 |
| - inspect.Parameter.KEYWORD_ONLY, |
22 |
| - inspect.Parameter.POSITIONAL_OR_KEYWORD, |
23 |
| - ): |
| 20 | + if [ |
| 21 | + param.name |
| 22 | + for param in sig.parameters.values() |
| 23 | + if param.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD |
| 24 | + or param.kind is inspect.Parameter.POSITIONAL_ONLY |
| 25 | + ]: |
| 26 | + raise TypeError( |
| 27 | + "Position-only and positional-or-keyword parameters are disallowed - " |
| 28 | + "use variable positional arguments to define whether this component has " |
| 29 | + "children and keyword-only to define its attributes." |
| 30 | + ) |
| 31 | + |
| 32 | + if "key" in sig.parameters: |
24 | 33 | raise TypeError(
|
25 | 34 | f"Component render function {function} uses reserved parameter 'key'"
|
26 | 35 | )
|
27 | 36 |
|
28 | 37 | @wraps(function)
|
29 |
| - def constructor(*args: Any, key: Optional[Any] = None, **kwargs: Any) -> Component: |
30 |
| - return Component(function, key, args, kwargs, sig) |
| 38 | + def constructor( |
| 39 | + *children: Any, key: Key | None = None, **attributes: Any |
| 40 | + ) -> Component: |
| 41 | + return Component(function, key, flatten_children(children), attributes, sig) |
31 | 42 |
|
32 | 43 | return constructor
|
33 | 44 |
|
34 | 45 |
|
35 | 46 | class Component:
|
36 | 47 | """An object for rending component models."""
|
37 | 48 |
|
38 |
| - __slots__ = "__weakref__", "_func", "_args", "_kwargs", "_sig", "key", "type" |
| 49 | + __slots__ = ( |
| 50 | + "__weakref__", |
| 51 | + "_func", |
| 52 | + "_children", |
| 53 | + "_attributes", |
| 54 | + "_sig", |
| 55 | + "key", |
| 56 | + "type", |
| 57 | + ) |
39 | 58 |
|
40 | 59 | def __init__(
|
41 | 60 | self,
|
42 |
| - function: Callable[..., ComponentType | VdomDict | str | None], |
43 |
| - key: Optional[Any], |
44 |
| - args: Tuple[Any, ...], |
45 |
| - kwargs: Dict[str, Any], |
| 61 | + function: Callable[..., RenderResult], |
| 62 | + key: Key | None, |
| 63 | + children: Sequence[Any], |
| 64 | + attributes: dict[str, Any], |
46 | 65 | sig: inspect.Signature,
|
47 | 66 | ) -> None:
|
48 | 67 | self.key = key
|
49 | 68 | self.type = function
|
50 |
| - self._args = args |
51 |
| - self._kwargs = kwargs |
| 69 | + self._children = children |
| 70 | + self._attributes = attributes |
52 | 71 | self._sig = sig
|
53 | 72 |
|
54 |
| - def render(self) -> ComponentType | VdomDict | str | None: |
55 |
| - return self.type(*self._args, **self._kwargs) |
| 73 | + def render(self) -> RenderResult: |
| 74 | + return self.type(*self._children, **self._attributes) |
56 | 75 |
|
57 | 76 | def should_render(self, new: Component) -> bool:
|
58 | 77 | return True
|
59 | 78 |
|
60 | 79 | def __repr__(self) -> str:
|
61 | 80 | try:
|
62 |
| - args = self._sig.bind(*self._args, **self._kwargs).arguments |
| 81 | + args = self._sig.bind(*self._children, **self._attributes).arguments |
63 | 82 | except TypeError:
|
64 | 83 | return f"{self.type.__name__}(...)"
|
65 | 84 | else:
|
|
0 commit comments