Foreach support for other data structures(dict, set, tuple) (#1029)
This commit is contained in:
parent
0c33ad19f6
commit
bb1cf4322e
@ -62,7 +62,7 @@
|
|||||||
{# Args: #}
|
{# Args: #}
|
||||||
{# component: component dictionary #}
|
{# component: component dictionary #}
|
||||||
{% macro render_iterable_tag(component) %}
|
{% macro render_iterable_tag(component) %}
|
||||||
{ {{- component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
|
{ {%- if component.iterable_type == 'dict' -%}Object.entries({{- component.iterable_state }}){%- else -%}{{- component.iterable_state }}{%- endif -%}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
|
||||||
{% for child in component.children %}
|
{% for child in component.children %}
|
||||||
{{ render(child) }}
|
{{ render(child) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -32,7 +32,7 @@ class Base(pydantic.BaseModel):
|
|||||||
Returns:
|
Returns:
|
||||||
The object as a json string.
|
The object as a json string.
|
||||||
"""
|
"""
|
||||||
return self.__config__.json_dumps(self.dict())
|
return self.__config__.json_dumps(self.dict(), default=list)
|
||||||
|
|
||||||
def set(self: PcType, **kwargs) -> PcType:
|
def set(self: PcType, **kwargs) -> PcType:
|
||||||
"""Set multiple fields and return the object.
|
"""Set multiple fields and return the object.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Create a list of components from an iterable."""
|
"""Create a list of components from an iterable."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable, List
|
from typing import Any, Callable, Dict, List, Set, Tuple, Union
|
||||||
|
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
from pynecone.components.layout.fragment import Fragment
|
from pynecone.components.layout.fragment import Fragment
|
||||||
@ -13,13 +13,15 @@ class Foreach(Component):
|
|||||||
"""A component that takes in an iterable and a render function and renders a list of components."""
|
"""A component that takes in an iterable and a render function and renders a list of components."""
|
||||||
|
|
||||||
# The iterable to create components from.
|
# The iterable to create components from.
|
||||||
iterable: Var[List]
|
iterable: Var[Union[List, Dict, Tuple, Set]]
|
||||||
|
|
||||||
# A function from the render args to the component.
|
# A function from the render args to the component.
|
||||||
render_fn: Callable = Fragment.create
|
render_fn: Callable = Fragment.create
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach:
|
def create(
|
||||||
|
cls, iterable: Var[Union[List, Dict, Tuple, Set]], render_fn: Callable, **props
|
||||||
|
) -> Foreach:
|
||||||
"""Create a foreach component.
|
"""Create a foreach component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -34,7 +36,11 @@ class Foreach(Component):
|
|||||||
TypeError: If the iterable is of type Any.
|
TypeError: If the iterable is of type Any.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
type_ = iterable.type_.__args__[0]
|
type_ = (
|
||||||
|
iterable.type_
|
||||||
|
if iterable.type_.mro()[0] == dict
|
||||||
|
else iterable.type_.__args__[0]
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
type_ = Any
|
type_ = Any
|
||||||
iterable = Var.create(iterable) # type: ignore
|
iterable = Var.create(iterable) # type: ignore
|
||||||
@ -61,7 +67,11 @@ class Foreach(Component):
|
|||||||
"""
|
"""
|
||||||
tag = self._render()
|
tag = self._render()
|
||||||
try:
|
try:
|
||||||
type_ = self.iterable.type_.__args__[0]
|
type_ = (
|
||||||
|
self.iterable.type_
|
||||||
|
if self.iterable.type_.mro()[0] == dict
|
||||||
|
else self.iterable.type_.__args__[0]
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
type_ = Any
|
type_ = Any
|
||||||
arg = BaseVar(
|
arg = BaseVar(
|
||||||
@ -84,4 +94,5 @@ class Foreach(Component):
|
|||||||
iterable_state=tag.iterable.full_name,
|
iterable_state=tag.iterable.full_name,
|
||||||
arg_name=arg.name,
|
arg_name=arg.name,
|
||||||
arg_index=index_arg,
|
arg_index=index_arg,
|
||||||
|
iterable_type=tag.iterable.type_.mro()[0].__name__,
|
||||||
)
|
)
|
||||||
|
@ -447,4 +447,4 @@ def json_dumps(obj: Any) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
A string
|
A string
|
||||||
"""
|
"""
|
||||||
return json.dumps(obj, ensure_ascii=False)
|
return json.dumps(obj, ensure_ascii=False, default=list)
|
||||||
|
@ -12,7 +12,7 @@ from pynecone.base import Base
|
|||||||
GenericType = Union[Type, _GenericAlias]
|
GenericType = Union[Type, _GenericAlias]
|
||||||
|
|
||||||
# Valid state var types.
|
# Valid state var types.
|
||||||
PrimitiveType = Union[int, float, bool, str, list, dict, tuple]
|
PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
|
||||||
StateVar = Union[PrimitiveType, Base, None]
|
StateVar = Union[PrimitiveType, Base, None]
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ class Var(ABC):
|
|||||||
):
|
):
|
||||||
if self.type_ == Any:
|
if self.type_ == Any:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Could not index into var of type Any. (If you are trying to index into a state var, add a type annotation to the var.)"
|
f"Could not index into var of type Any. (If you are trying to index into a state var, add the correct type annotation to the var.)"
|
||||||
)
|
)
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Var {self.name} of type {self.type_} does not support indexing."
|
f"Var {self.name} of type {self.type_} does not support indexing."
|
||||||
|
177
tests/components/layout/test_foreach.py
Normal file
177
tests/components/layout/test_foreach.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
from typing import Dict, List, Set, Tuple
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from pynecone.components import box, foreach, text
|
||||||
|
from pynecone.components.layout import Foreach
|
||||||
|
from pynecone.state import State
|
||||||
|
|
||||||
|
|
||||||
|
class ForEachState(State):
|
||||||
|
"""The test state."""
|
||||||
|
|
||||||
|
color_a: List[str] = ["red", "yellow"]
|
||||||
|
color_b: List[Dict[str, str]] = [
|
||||||
|
{
|
||||||
|
"name": "red",
|
||||||
|
},
|
||||||
|
{"name": "yellow"},
|
||||||
|
]
|
||||||
|
color_c: List[Dict[str, List[str]]] = [{"shades": ["light-red"]}]
|
||||||
|
color_d: Dict[str, str] = {"category": "primary", "name": "red"}
|
||||||
|
color_e: Dict[str, List[str]] = {
|
||||||
|
"red": ["orange", "yellow"],
|
||||||
|
"yellow": ["orange", "green"],
|
||||||
|
}
|
||||||
|
color_f: Dict[str, Dict[str, List[Dict[str, str]]]] = {
|
||||||
|
"primary": {"red": [{"shade": "dark"}]}
|
||||||
|
}
|
||||||
|
color_g: Tuple[str, str] = (
|
||||||
|
"red",
|
||||||
|
"yellow",
|
||||||
|
)
|
||||||
|
color_h: Set[str] = {"red", "green"}
|
||||||
|
|
||||||
|
|
||||||
|
def display_a(color):
|
||||||
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
|
def display_b(color):
|
||||||
|
return box(text(color["name"]))
|
||||||
|
|
||||||
|
|
||||||
|
def display_c(color):
|
||||||
|
return box(text(color["shades"][0]))
|
||||||
|
|
||||||
|
|
||||||
|
def display_d(color):
|
||||||
|
return box(text(color[0]), text(color[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def display_e(color):
|
||||||
|
# color is a key-value pair list similar to `dict.items()`
|
||||||
|
return box(text(color[0]), text(color[1][0]))
|
||||||
|
|
||||||
|
|
||||||
|
def display_f(color):
|
||||||
|
return box(text(color[0]), text(color[1]["red"][0]["shade"]))
|
||||||
|
|
||||||
|
|
||||||
|
def show_item(item):
|
||||||
|
return text(item[1][0]["shade"])
|
||||||
|
|
||||||
|
|
||||||
|
def display_f1(color):
|
||||||
|
return box(text(foreach(color[1], show_item)))
|
||||||
|
|
||||||
|
|
||||||
|
def display_g(color):
|
||||||
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
|
def display_h(color):
|
||||||
|
return box(text(color))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"state_var, render_fn, render_dict",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ForEachState.color_a,
|
||||||
|
display_a,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_a",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "list",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_b,
|
||||||
|
display_b,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_b",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "list",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_c,
|
||||||
|
display_c,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_c",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "list",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_d,
|
||||||
|
display_d,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_d",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "dict",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_e,
|
||||||
|
display_e,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_e",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "dict",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_f,
|
||||||
|
display_f,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_f",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "dict",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_f,
|
||||||
|
display_f1,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_f",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "dict",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_g,
|
||||||
|
display_g,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_g",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "tuple",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ForEachState.color_h,
|
||||||
|
display_h,
|
||||||
|
{
|
||||||
|
"iterable_state": "for_each_state.color_h",
|
||||||
|
"arg_index": "i",
|
||||||
|
"iterable_type": "set",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_foreach_render(state_var, render_fn, render_dict):
|
||||||
|
"""Test that the foreach component renders without error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state_var: the state var.
|
||||||
|
render_fn: The render callable
|
||||||
|
render_dict: return dict on calling `component.render`
|
||||||
|
"""
|
||||||
|
component = Foreach.create(state_var, render_fn)
|
||||||
|
|
||||||
|
rend = component.render()
|
||||||
|
|
||||||
|
assert rend["iterable_state"] == render_dict["iterable_state"]
|
||||||
|
assert rend["arg_index"] == render_dict["arg_index"]
|
||||||
|
assert rend["iterable_type"] == render_dict["iterable_type"]
|
@ -28,6 +28,7 @@ class TestState(State):
|
|||||||
num1: int
|
num1: int
|
||||||
num2: float = 3.14
|
num2: float = 3.14
|
||||||
key: str
|
key: str
|
||||||
|
map_key: str = "a"
|
||||||
array: List[float] = [1, 2, 3.14]
|
array: List[float] = [1, 2, 3.14]
|
||||||
mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
|
mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
|
||||||
obj: Object = Object()
|
obj: Object = Object()
|
||||||
@ -190,6 +191,7 @@ def test_class_vars(test_state):
|
|||||||
"num1",
|
"num1",
|
||||||
"num2",
|
"num2",
|
||||||
"key",
|
"key",
|
||||||
|
"map_key",
|
||||||
"array",
|
"array",
|
||||||
"mapping",
|
"mapping",
|
||||||
"obj",
|
"obj",
|
||||||
@ -281,6 +283,9 @@ def test_class_indexing_with_vars():
|
|||||||
prop = TestState.mapping["a"][TestState.num1]
|
prop = TestState.mapping["a"][TestState.num1]
|
||||||
assert str(prop) == '{test_state.mapping["a"].at(test_state.num1)}'
|
assert str(prop) == '{test_state.mapping["a"].at(test_state.num1)}'
|
||||||
|
|
||||||
|
prop = TestState.mapping[TestState.map_key]
|
||||||
|
assert str(prop) == "{test_state.mapping[test_state.map_key]}"
|
||||||
|
|
||||||
|
|
||||||
def test_class_attributes():
|
def test_class_attributes():
|
||||||
"""Test that we can get class attributes."""
|
"""Test that we can get class attributes."""
|
||||||
|
Loading…
Reference in New Issue
Block a user