Foreach support for other data structures(dict, set, tuple) (#1029)
This commit is contained in:
parent
0c33ad19f6
commit
bb1cf4322e
@ -62,7 +62,7 @@
|
||||
{# Args: #}
|
||||
{# component: component dictionary #}
|
||||
{% 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 %}
|
||||
{{ render(child) }}
|
||||
{% endfor %}
|
||||
|
@ -32,7 +32,7 @@ class Base(pydantic.BaseModel):
|
||||
Returns:
|
||||
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:
|
||||
"""Set multiple fields and return the object.
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Create a list of components from an iterable."""
|
||||
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.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."""
|
||||
|
||||
# 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.
|
||||
render_fn: Callable = Fragment.create
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@ -34,7 +36,11 @@ class Foreach(Component):
|
||||
TypeError: If the iterable is of type Any.
|
||||
"""
|
||||
try:
|
||||
type_ = iterable.type_.__args__[0]
|
||||
type_ = (
|
||||
iterable.type_
|
||||
if iterable.type_.mro()[0] == dict
|
||||
else iterable.type_.__args__[0]
|
||||
)
|
||||
except Exception:
|
||||
type_ = Any
|
||||
iterable = Var.create(iterable) # type: ignore
|
||||
@ -61,7 +67,11 @@ class Foreach(Component):
|
||||
"""
|
||||
tag = self._render()
|
||||
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:
|
||||
type_ = Any
|
||||
arg = BaseVar(
|
||||
@ -84,4 +94,5 @@ class Foreach(Component):
|
||||
iterable_state=tag.iterable.full_name,
|
||||
arg_name=arg.name,
|
||||
arg_index=index_arg,
|
||||
iterable_type=tag.iterable.type_.mro()[0].__name__,
|
||||
)
|
||||
|
@ -447,4 +447,4 @@ def json_dumps(obj: Any) -> str:
|
||||
Returns:
|
||||
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]
|
||||
|
||||
# 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]
|
||||
|
||||
|
||||
|
@ -205,7 +205,7 @@ class Var(ABC):
|
||||
):
|
||||
if self.type_ == Any:
|
||||
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(
|
||||
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
|
||||
num2: float = 3.14
|
||||
key: str
|
||||
map_key: str = "a"
|
||||
array: List[float] = [1, 2, 3.14]
|
||||
mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
|
||||
obj: Object = Object()
|
||||
@ -190,6 +191,7 @@ def test_class_vars(test_state):
|
||||
"num1",
|
||||
"num2",
|
||||
"key",
|
||||
"map_key",
|
||||
"array",
|
||||
"mapping",
|
||||
"obj",
|
||||
@ -281,6 +283,9 @@ def test_class_indexing_with_vars():
|
||||
prop = TestState.mapping["a"][TestState.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():
|
||||
"""Test that we can get class attributes."""
|
||||
|
Loading…
Reference in New Issue
Block a user