|
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